From 1703d96a48111a2fa23069a233bba461efa4f326 Mon Sep 17 00:00:00 2001 From: Gregory Tsipenyuk Date: Mon, 2 Jun 2025 09:52:10 -0400 Subject: [PATCH 01/18] fix: Add AMMv1_3 amendment (#5203) * Add AMM bid/create/deposit/swap/withdraw/vote invariants: - Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`. - Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change. - Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`. - Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore` and `LPTokens` don't change. - Vote: `LPTokens` and pool balances don't change. - All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens are withdrawn. * Add AMM deposit and withdraw rounding to ensure AMM invariant: - On deposit, tokens out are rounded downward and deposit amount is rounded upward. - On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward. * Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer. * Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account. --- include/xrpl/protocol/Feature.h | 2 +- include/xrpl/protocol/IOUAmount.h | 6 + include/xrpl/protocol/Rules.h | 3 + include/xrpl/protocol/detail/features.macro | 1 + src/libxrpl/protocol/Rules.cpp | 8 + src/test/app/AMMClawback_test.cpp | 543 ++++++-- src/test/app/AMMExtended_test.cpp | 7 + src/test/app/AMM_test.cpp | 1346 +++++++++++++++---- src/test/jtx/AMM.h | 11 +- src/test/jtx/AMMTest.h | 14 + src/test/jtx/Env.h | 6 + src/test/jtx/impl/AMM.cpp | 19 +- src/test/jtx/impl/AMMTest.cpp | 34 +- src/test/rpc/AMMInfo_test.cpp | 186 +-- src/xrpld/app/misc/AMMHelpers.h | 151 ++- src/xrpld/app/misc/detail/AMMHelpers.cpp | 204 ++- src/xrpld/app/tx/detail/AMMBid.cpp | 19 +- src/xrpld/app/tx/detail/AMMDeposit.cpp | 156 ++- src/xrpld/app/tx/detail/AMMWithdraw.cpp | 143 +- src/xrpld/app/tx/detail/AMMWithdraw.h | 2 +- src/xrpld/app/tx/detail/InvariantCheck.cpp | 308 +++++ src/xrpld/app/tx/detail/InvariantCheck.h | 66 +- src/xrpld/app/tx/detail/Offer.h | 20 +- 23 files changed, 2658 insertions(+), 597 deletions(-) diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index ed0732930..3f7a39778 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -80,7 +80,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 = 113; +static constexpr std::size_t numFeatures = 114; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index e89feb123..d057b2b74 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -97,6 +97,12 @@ public: static IOUAmount minPositiveAmount(); + + friend std::ostream& + operator<<(std::ostream& os, IOUAmount const& x) + { + return os << to_string(x); + } }; inline IOUAmount::IOUAmount(beast::Zero) diff --git a/include/xrpl/protocol/Rules.h b/include/xrpl/protocol/Rules.h index 7d6d9b0a9..a6632c23a 100644 --- a/include/xrpl/protocol/Rules.h +++ b/include/xrpl/protocol/Rules.h @@ -28,6 +28,9 @@ namespace ripple { +bool +isFeatureEnabled(uint256 const& feature); + class DigestAwareReadView; /** Rules controlling protocol behavior. */ diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 3f48ac714..c04e40094 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -31,6 +31,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index 7043acff9..89cb91a0a 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -153,4 +153,12 @@ Rules::operator!=(Rules const& other) const { return !(*this == other); } + +bool +isFeatureEnabled(uint256 const& feature) +{ + auto const& rules = getCurrentTransactionRules(); + return rules && rules->enabled(feature); +} + } // namespace ripple diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index c547a537b..9ccd59b58 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -581,8 +581,12 @@ class AMMClawback_test : public jtx::AMMTest AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999579, -12})); // gw clawback 1000 USD from the AMM pool env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), @@ -601,12 +605,20 @@ class AMMClawback_test : public jtx::AMMTest // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the // current balance is 3000 USD and 3750 EUR. - BEAST_EXPECT(amm.expectBalances( - USD(3000), EUR(3750), IOUAmount{3354101966249685, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(3000), EUR(3750), IOUAmount{3354101966249685, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(3000), EUR(3750), IOUAmount{3354101966249684, -12})); // Alice has 3/4 of its initial lptokens Left. - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{3354101966249685, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{3354101966249685, -12})); + else + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{3354101966249684, -12})); // gw clawback another 500 USD from the AMM pool. env(amm::ammClawback(gw, alice, USD, EUR, USD(500)), @@ -617,14 +629,21 @@ class AMMClawback_test : public jtx::AMMTest // AMM pool. env.require(balance(alice, gw["USD"](2000))); - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2500000000000001), -12}, - STAmount{EUR, UINT64_C(3125000000000001), -12}, - IOUAmount{2795084971874738, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2500000000000001), -12}, + STAmount{EUR, UINT64_C(3125000000000001), -12}, + IOUAmount{2795084971874738, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(2500), EUR(3125), IOUAmount{2795084971874737, -12})); - BEAST_EXPECT( - env.balance(alice, EUR) == - STAmount(EUR, UINT64_C(2874999999999999), -12)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(2874999999999999), -12)); + else + BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875)); // gw clawback small amount, 1 USD. env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS)); @@ -633,14 +652,21 @@ class AMMClawback_test : public jtx::AMMTest // Another 1 USD / 1.25 EUR was withdrawn. env.require(balance(alice, gw["USD"](2000))); - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2499000000000002), -12}, - STAmount{EUR, UINT64_C(3123750000000002), -12}, - IOUAmount{2793966937885989, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2499000000000002), -12}, + STAmount{EUR, UINT64_C(3123750000000002), -12}, + IOUAmount{2793966937885989, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12})); - BEAST_EXPECT( - env.balance(alice, EUR) == - STAmount(EUR, UINT64_C(2876249999999998), -12)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(2'876'249999999998), -12)); + else + BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25)); // gw clawback 4000 USD, exceeding the current balance. We // will clawback all. @@ -713,14 +739,26 @@ class AMMClawback_test : public jtx::AMMTest // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR. AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS)); - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); amm2.deposit(alice, EUR(1000), XRP(3000)); - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); amm2.deposit(bob, EUR(1000), XRP(3000)); - BEAST_EXPECT(amm2.expectBalances( - EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm2.expectBalances( + EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9})); env.close(); auto aliceXrpBalance = env.balance(alice, XRP); @@ -743,10 +781,18 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT( expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); - BEAST_EXPECT(amm.expectBalances( - USD(2500), XRP(5000), IOUAmount{3535533905932738, -9})); - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(2500), XRP(5000), IOUAmount{3535533905932738, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(2500), XRP(5000), IOUAmount{3535533905932737, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{7071067811865480, -10})); + else + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{7071067811865474, -10})); BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9})); @@ -760,14 +806,26 @@ class AMMClawback_test : public jtx::AMMTest // Bob gets 20 XRP back. BEAST_EXPECT( expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20))); - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2490000000000001), -12}, - XRP(4980), - IOUAmount{3521391770309008, -9})); - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10})); - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2490000000000001), -12}, + XRP(4980), + IOUAmount{3521391770309008, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{7071067811865480, -10})); + else + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{7071067811865474, -10})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + else + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); // gw2 clawback 200 EUR from amm2. env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)), @@ -780,12 +838,24 @@ class AMMClawback_test : public jtx::AMMTest // Alice gets 600 XRP back. BEAST_EXPECT(expectLedgerEntryRoot( env, alice, aliceXrpBalance + XRP(1000) + XRP(600))); - BEAST_EXPECT(amm2.expectBalances( - EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9})); - BEAST_EXPECT( - amm2.expectLPTokens(alice, IOUAmount{1385640646055103, -9})); - BEAST_EXPECT( - amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm2.expectLPTokens( + alice, IOUAmount{1385640646055103, -9})); + else + BEAST_EXPECT(amm2.expectLPTokens( + alice, IOUAmount{1385640646055102, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9})); + else + BEAST_EXPECT( + amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9})); // gw claw back 1000 USD from alice in amm, which exceeds alice's // balance. This will clawback all the remaining LP tokens of alice @@ -798,17 +868,34 @@ class AMMClawback_test : public jtx::AMMTest env.require(balance(bob, gw["USD"](4000))); // Alice gets 1000 XRP back. - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - + XRPAmount{1})); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(1990000000000001), -12}, - XRP(3980), - IOUAmount{2814284989122460, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + else + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(1990000000000001), -12}, + XRP(3980), + IOUAmount{2814284989122460, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(1'990), + XRPAmount{3'980'000'001}, + IOUAmount{2814284989122459, -9})); // gw clawback 1000 USD from bob in amm, which also exceeds bob's // balance in amm. All bob's lptoken in amm will be consumed, which @@ -820,10 +907,17 @@ class AMMClawback_test : public jtx::AMMTest env.require(balance(alice, gw["USD"](5000))); env.require(balance(bob, gw["USD"](4000))); - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - + XRPAmount{1})); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, bobXrpBalance + XRP(20) + XRP(1980))); @@ -843,21 +937,32 @@ class AMMClawback_test : public jtx::AMMTest // Alice gets another 2400 XRP back, bob's XRP balance remains the // same. - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + + XRP(2400))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + + XRP(2400) - XRPAmount{1})); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, bobXrpBalance + XRP(20) + XRP(1980))); // Alice now does not have any lptoken in amm2 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); - // gw2 claw back 2000 EUR from bib in amm2, which exceeds bob's + // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's // balance. All bob's lptokens will be consumed, which corresponds // to 1000EUR / 3000 XRP. env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)), @@ -869,11 +974,18 @@ class AMMClawback_test : public jtx::AMMTest // Bob gets another 3000 XRP back. Alice's XRP balance remains the // same. - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + + XRP(2400))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + + XRP(2400) - XRPAmount{1})); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000))); @@ -881,8 +993,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0))); - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); } } @@ -940,21 +1056,45 @@ class AMMClawback_test : public jtx::AMMTest AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999579, -12})); amm.deposit(bob, USD(2000), EUR(2500)); - BEAST_EXPECT(amm.expectBalances( - USD(6000), EUR(7500), IOUAmount{6708203932499370, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(6000), EUR(7500), IOUAmount{6708203932499370, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(6000), EUR(7500), IOUAmount{6708203932499368, -12})); amm.deposit(carol, USD(1000), EUR(1250)); - BEAST_EXPECT(amm.expectBalances( - USD(7000), EUR(8750), IOUAmount{7826237921249265, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(7000), EUR(8750), IOUAmount{7826237921249265, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(7000), EUR(8750), IOUAmount{7826237921249262, -12})); - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12})); - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12})); - BEAST_EXPECT( - amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{4472135954999580, -12})); + else + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{4472135954999579, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12})); + else + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + carol, IOUAmount{1118033988749895, -12})); + else + BEAST_EXPECT(amm.expectLPTokens( + carol, IOUAmount{1118033988749894, -12})); env.require(balance(alice, gw["USD"](2000))); env.require(balance(alice, gw2["EUR"](1000))); @@ -968,16 +1108,30 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(4999999999999999), -12}, - STAmount{EUR, UINT64_C(6249999999999999), -12}, - IOUAmount{5590169943749475, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(4999999999999999), -12}, + STAmount{EUR, UINT64_C(6249999999999999), -12}, + IOUAmount{5590169943749475, -12})); + else + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(5000000000000001), -12}, + STAmount{EUR, UINT64_C(6250000000000001), -12}, + IOUAmount{5590169943749473, -12})); - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{4472135954999580, -12})); + else + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{4472135954999579, -12})); BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); - BEAST_EXPECT( - amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + carol, IOUAmount{1118033988749895, -12})); + else + BEAST_EXPECT(amm.expectLPTokens( + carol, IOUAmount{1118033988749894, -12})); // Bob will get 2500 EUR back. env.require(balance(alice, gw["USD"](2000))); @@ -986,9 +1140,14 @@ class AMMClawback_test : public jtx::AMMTest env.balance(bob, USD) == STAmount(USD, UINT64_C(3000000000000000), -12)); - BEAST_EXPECT( - env.balance(bob, EUR) == - STAmount(EUR, UINT64_C(5000000000000001), -12)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + env.balance(bob, EUR) == + STAmount(EUR, UINT64_C(5000000000000001), -12)); + else + BEAST_EXPECT( + env.balance(bob, EUR) == + STAmount(EUR, UINT64_C(4999999999999999), -12)); env.require(balance(carol, gw["USD"](3000))); env.require(balance(carol, gw2["EUR"](2750))); @@ -996,13 +1155,23 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(3999999999999999), -12}, - STAmount{EUR, UINT64_C(4999999999999999), -12}, - IOUAmount{4472135954999580, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(3999999999999999), -12}, + STAmount{EUR, UINT64_C(4999999999999999), -12}, + IOUAmount{4472135954999580, -12})); + else + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(4000000000000001), -12}, + STAmount{EUR, UINT64_C(5000000000000002), -12}, + IOUAmount{4472135954999579, -12})); - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{4472135954999580, -12})); + else + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{4472135954999579, -12})); BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0))); @@ -1041,14 +1210,26 @@ class AMMClawback_test : public jtx::AMMTest // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD. AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS)); - BEAST_EXPECT(amm.expectBalances( - USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(10000), XRP(2000), IOUAmount{4472135954999579, -9})); amm.deposit(alice, USD(1000), XRP(200)); - BEAST_EXPECT(amm.expectBalances( - USD(11000), XRP(2200), IOUAmount{4919349550499538, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(11000), XRP(2200), IOUAmount{4919349550499538, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(11000), XRP(2200), IOUAmount{4919349550499536, -9})); amm.deposit(bob, USD(2000), XRP(400)); - BEAST_EXPECT(amm.expectBalances( - USD(13000), XRP(2600), IOUAmount{5813776741499453, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(13000), XRP(2600), IOUAmount{5813776741499453, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(13000), XRP(2600), IOUAmount{5813776741499451, -9})); env.close(); auto aliceXrpBalance = env.balance(alice, XRP); @@ -1058,18 +1239,34 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(12000), XRP(2400), IOUAmount{5366563145999495, -9})); - BEAST_EXPECT( - expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(200))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(12000), XRP(2400), IOUAmount{5366563145999495, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(12000), + XRPAmount(2400000001), + IOUAmount{5366563145999494, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(200))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1})); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); // gw clawback all bob's USD in amm. (2000 USD / 400 XRP) env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(10000), + XRPAmount(2000000001), + IOUAmount{4472135954999579, -9})); BEAST_EXPECT( expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400))); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); @@ -1125,10 +1322,12 @@ class AMMClawback_test : public jtx::AMMTest amm.deposit(bob, USD(4000), EUR(1000)); BEAST_EXPECT( amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); - amm.deposit(carol, USD(2000), EUR(500)); + if (!features[fixAMMv1_3]) + amm.deposit(carol, USD(2000), EUR(500)); + else + amm.deposit(carol, USD(2000.25), EUR(500)); BEAST_EXPECT( amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000))); - // gw clawback 1000 USD from carol. env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS)); env.close(); @@ -1142,7 +1341,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + else + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); // 250 EUR goes back to carol. BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); @@ -1164,7 +1368,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); // 250 EUR did not go back to bob because tfClawTwoAssets is set. BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + else + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); // gw clawback all USD from alice and set tfClawTwoAssets. @@ -1181,7 +1390,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + else + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); } @@ -1366,12 +1580,21 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 1000 EUR from gw. env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(4500), - STAmount(EUR, UINT64_C(9000000000000001), -12), - IOUAmount{6363961030678928, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(4500), + STAmount(EUR, UINT64_C(9000000000000001), -12), + IOUAmount{6363961030678928, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(4500), EUR(9000), IOUAmount{6363961030678928, -12})); - BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1384,12 +1607,21 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 4000 EUR from alice. env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(2500), - STAmount(EUR, UINT64_C(5000000000000001), -12), - IOUAmount{3535533905932738, -12})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(2500), + STAmount(EUR, UINT64_C(5000000000000001), -12), + IOUAmount{3535533905932738, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(2500), EUR(5000), IOUAmount{3535533905932738, -12})); - BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12})); @@ -1653,7 +1885,10 @@ class AMMClawback_test : public jtx::AMMTest amm.deposit(bob, USD(4000), EUR(1000)); BEAST_EXPECT( amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); - amm.deposit(carol, USD(2000), EUR(500)); + if (!features[fixAMMv1_3]) + amm.deposit(carol, USD(2000), EUR(500)); + else + amm.deposit(carol, USD(2000.25), EUR(500)); BEAST_EXPECT( amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000))); @@ -1675,7 +1910,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + else + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); // 250 EUR goes back to carol. BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); @@ -1697,7 +1937,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); // 250 EUR did not go back to bob because tfClawTwoAssets is set. BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + else + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); // gw clawback all USD from alice and set tfClawTwoAssets. @@ -1715,7 +1960,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); + else + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); } } @@ -1763,13 +2013,23 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(5656854249492380), -13), - XRP(70.710678), - IOUAmount(200000))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5656854249492380), -13), + XRP(70.710678), + IOUAmount(200000))); + else + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(565'685424949238), -12), + XRP(70.710679), + IOUAmount(200000))); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(29.289322))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(29.289322))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(29.289321))); } void @@ -1780,13 +2040,18 @@ class AMMClawback_test : public jtx::AMMTest testFeatureDisabled(all - featureAMMClawback); testAMMClawbackSpecificAmount(all); testAMMClawbackExceedBalance(all); + testAMMClawbackExceedBalance(all - fixAMMv1_3); testAMMClawbackAll(all); + testAMMClawbackAll(all - fixAMMv1_3); testAMMClawbackSameIssuerAssets(all); + testAMMClawbackSameIssuerAssets(all - fixAMMv1_3); testAMMClawbackSameCurrency(all); testAMMClawbackIssuesEachOther(all); testNotHoldingLptoken(all); testAssetFrozen(all); + testAssetFrozen(all - fixAMMv1_3); testSingleDepositAndClawback(all); + testSingleDepositAndClawback(all - fixAMMv1_3); } }; BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple); diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 16e66d803..d6142d7d3 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -1405,6 +1405,7 @@ private: using namespace jtx; FeatureBitset const all{supported_amendments()}; testRmFundedOffer(all); + testRmFundedOffer(all - fixAMMv1_3); testEnforceNoRipple(all); testFillModes(all); testOfferCrossWithXRP(all); @@ -1418,6 +1419,7 @@ private: testOfferCreateThenCross(all); testSellFlagExceedLimit(all); testGatewayCrossCurrency(all); + testGatewayCrossCurrency(all - fixAMMv1_3); testBridgedCross(all); testSellWithFillOrKill(all); testTransferRateOffer(all); @@ -1425,6 +1427,7 @@ private: testBadPathAssert(all); testSellFlagBasic(all); testDirectToDirectPath(all); + testDirectToDirectPath(all - fixAMMv1_3); testRequireAuth(all); testMissingAuth(all); } @@ -3835,7 +3838,9 @@ private: testBookStep(all); testBookStep(all | ownerPaysFee); testTransferRate(all | ownerPaysFee); + testTransferRate((all - fixAMMv1_3) | ownerPaysFee); testTransferRateNoOwnerFee(all); + testTransferRateNoOwnerFee(all - fixAMMv1_3); testLimitQuality(); testXRPPathLoop(); } @@ -3846,6 +3851,7 @@ private: using namespace jtx; FeatureBitset const all{supported_amendments()}; testStepLimit(all); + testStepLimit(all - fixAMMv1_3); } void @@ -3854,6 +3860,7 @@ private: using namespace jtx; FeatureBitset const all{supported_amendments()}; test_convert_all_of_an_asset(all); + test_convert_all_of_an_asset(all - fixAMMv1_3); } void diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index ad1e761d9..8d5595940 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -821,21 +820,6 @@ private: std::nullopt, ter(tecAMM_FAILED)); - // Tiny deposit - ammAlice.deposit( - carol, - IOUAmount{1, -4}, - std::nullopt, - std::nullopt, - ter(temBAD_AMOUNT)); - ammAlice.deposit( - carol, - STAmount{USD, 1, -12}, - std::nullopt, - std::nullopt, - std::nullopt, - ter(tecAMM_INVALID_TOKENS)); - // Deposit non-empty AMM ammAlice.deposit( carol, @@ -846,6 +830,34 @@ private: ter(tecAMM_NOT_EMPTY)); }); + // Tiny deposit + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const enabledv1_3 = + env.current()->rules().enabled(fixAMMv1_3); + auto const err = + !enabledv1_3 ? ter(temBAD_AMOUNT) : ter(tesSUCCESS); + // Pre-amendment XRP deposit side is rounded to 0 + // and deposit fails. + // Post-amendment XRP deposit side is rounded to 1 + // and deposit succeeds. + ammAlice.deposit( + carol, IOUAmount{1, -4}, std::nullopt, std::nullopt, err); + // Pre/post-amendment LPTokens is rounded to 0 and deposit + // fails with tecAMM_INVALID_TOKENS. + ammAlice.deposit( + carol, + STAmount{USD, 1, -12}, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + }, + std::nullopt, + 0, + std::nullopt, + {features, features - fixAMMv1_3}); + // Invalid AMM testAMM([&](AMM& ammAlice, Env& env) { ammAlice.withdrawAll(alice); @@ -1301,6 +1313,53 @@ private: std::nullopt, ter(tecAMM_FAILED)); }); + + // Equal deposit, tokens rounded to 0 + testAMM([&](AMM& amm, Env& env) { + amm.deposit(DepositArg{ + .tokens = IOUAmount{1, -12}, + .err = ter(tecAMM_INVALID_TOKENS)}); + }); + + // Equal deposit limit, tokens rounded to 0 + testAMM( + [&](AMM& amm, Env& env) { + amm.deposit(DepositArg{ + .asset1In = STAmount{USD, 1, -15}, + .asset2In = XRPAmount{1}, + .err = ter(tecAMM_INVALID_TOKENS)}); + }, + {.pool = {{USD(1'000'000), XRP(1'000'000)}}, + .features = {features - fixAMMv1_3}}); + testAMM([&](AMM& amm, Env& env) { + amm.deposit(DepositArg{ + .asset1In = STAmount{USD, 1, -15}, + .asset2In = XRPAmount{1}, + .err = ter(tecAMM_INVALID_TOKENS)}); + }); + + // Single deposit by asset, tokens rounded to 0 + testAMM([&](AMM& amm, Env& env) { + amm.deposit(DepositArg{ + .asset1In = STAmount{USD, 1, -15}, + .err = ter(tecAMM_INVALID_TOKENS)}); + }); + + // Single deposit by tokens, tokens rounded to 0 + testAMM([&](AMM& amm, Env& env) { + amm.deposit(DepositArg{ + .tokens = IOUAmount{1, -10}, + .asset1In = STAmount{USD, 1, -15}, + .err = ter(tecAMM_INVALID_TOKENS)}); + }); + + // Single deposit with eprice, tokens rounded to 0 + testAMM([&](AMM& amm, Env& env) { + amm.deposit(DepositArg{ + .asset1In = STAmount{USD, 1, -15}, + .maxEP = STAmount{USD, 1, -1}, + .err = ter(tecAMM_INVALID_TOKENS)}); + }); } void @@ -1309,6 +1368,7 @@ private: testcase("Deposit"); using namespace jtx; + auto const all = supported_amendments(); // Equal deposit: 1000000 tokens, 10% of the current pool testAMM([&](AMM& ammAlice, Env& env) { @@ -1513,8 +1573,9 @@ private: }); // Issuer create/deposit + for (auto const& feat : {all, all - fixAMMv1_3}) { - Env env(*this); + Env env(*this, feat); env.fund(XRP(30000), gw); AMM ammGw(env, gw, XRP(10'000), USD(10'000)); BEAST_EXPECT( @@ -1608,6 +1669,7 @@ private: testcase("Invalid Withdraw"); using namespace jtx; + auto const all = supported_amendments(); testAMM( [&](AMM& ammAlice, Env& env) { @@ -1901,16 +1963,6 @@ private: ammAlice.withdraw( carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE)); - // Withdraw entire one side of the pool. - // Equal withdraw but due to XRP precision limit, - // this results in full withdraw of XRP pool only, - // while leaving a tiny amount in USD pool. - ammAlice.withdraw( - alice, - IOUAmount{9'999'999'9999, -4}, - std::nullopt, - std::nullopt, - ter(tecAMM_BALANCE)); // Withdrawing from one side. // XRP by tokens ammAlice.withdraw( @@ -1942,6 +1994,57 @@ private: ter(tecAMM_BALANCE)); }); + testAMM( + [&](AMM& ammAlice, Env& env) { + // Withdraw entire one side of the pool. + // Pre-amendment: + // Equal withdraw but due to XRP rounding + // this results in full withdraw of XRP pool only, + // while leaving a tiny amount in USD pool. + // Post-amendment: + // Most of the pool is withdrawn with remaining tiny amounts + auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) + : ter(tecAMM_BALANCE); + ammAlice.withdraw( + alice, + IOUAmount{9'999'999'9999, -4}, + std::nullopt, + std::nullopt, + err); + if (env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(1), STAmount{USD, 1, -7}, IOUAmount{1, -4})); + }, + std::nullopt, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + // Similar to above with even smaller remaining amount + // is it ok that the pool is unbalanced? + // Withdraw entire one side of the pool. + // Equal withdraw but due to XRP precision limit, + // this results in full withdraw of XRP pool only, + // while leaving a tiny amount in USD pool. + auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) + : ter(tecAMM_BALANCE); + ammAlice.withdraw( + alice, + IOUAmount{9'999'999'999999999, -9}, + std::nullopt, + std::nullopt, + err); + if (env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(1), STAmount{USD, 1, -11}, IOUAmount{1, -8})); + }, + std::nullopt, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); + // Invalid AMM testAMM([&](AMM& ammAlice, Env& env) { ammAlice.withdrawAll(alice); @@ -2005,15 +2108,19 @@ private: // Withdraw with EPrice limit. Fails to withdraw, calculated tokens // to withdraw are 0. - testAMM([&](AMM& ammAlice, Env&) { - ammAlice.deposit(carol, 1'000'000); - ammAlice.withdraw( - carol, - USD(100), - std::nullopt, - IOUAmount{500, 0}, - ter(tecAMM_FAILED)); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000); + auto const err = env.enabled(fixAMMv1_3) + ? ter(tecAMM_INVALID_TOKENS) + : ter(tecAMM_FAILED); + ammAlice.withdraw( + carol, USD(100), std::nullopt, IOUAmount{500, 0}, err); + }, + std::nullopt, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); // Withdraw with EPrice limit. Fails to withdraw, calculated tokens // to withdraw are greater than the LP shares. @@ -2078,14 +2185,19 @@ private: // Withdraw close to one side of the pool. Account's LP tokens // are rounded to all LP tokens. - testAMM([&](AMM& ammAlice, Env&) { - ammAlice.withdraw( - alice, - STAmount{USD, UINT64_C(9'999'999999999999), -12}, - std::nullopt, - std::nullopt, - ter(tecAMM_BALANCE)); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const err = env.enabled(fixAMMv1_3) + ? ter(tecINVARIANT_FAILED) + : ter(tecAMM_BALANCE); + ammAlice.withdraw( + alice, + STAmount{USD, UINT64_C(9'999'999999999999), -12}, + std::nullopt, + std::nullopt, + err); + }, + {.features = {all, all - fixAMMv1_3}, .noLog = true}); // Tiny withdraw testAMM([&](AMM& ammAlice, Env&) { @@ -2116,6 +2228,17 @@ private: XRPAmount{1}, std::nullopt, ter(tecAMM_INVALID_TOKENS)); + ammAlice.withdraw(WithdrawArg{ + .tokens = IOUAmount{1, -10}, + .err = ter(tecAMM_INVALID_TOKENS)}); + ammAlice.withdraw(WithdrawArg{ + .asset1Out = STAmount{USD, 1, -15}, + .asset2Out = XRPAmount{1}, + .err = ter(tecAMM_INVALID_TOKENS)}); + ammAlice.withdraw(WithdrawArg{ + .tokens = IOUAmount{1, -10}, + .asset1Out = STAmount{USD, 1, -15}, + .err = ter(tecAMM_INVALID_TOKENS)}); }); } @@ -2125,6 +2248,7 @@ private: testcase("Withdraw"); using namespace jtx; + auto const all = supported_amendments(); // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current // pool @@ -2178,11 +2302,24 @@ private: }); // Single withdrawal by amount XRP1000 - testAMM([&](AMM& ammAlice, Env&) { - ammAlice.withdraw(alice, XRP(1'000)); - BEAST_EXPECT(ammAlice.expectBalances( - XRP(9'000), USD(10'000), IOUAmount{9'486'832'98050514, -8})); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdraw(alice, XRP(1'000)); + if (!env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(9'000), + USD(10'000), + IOUAmount{9'486'832'98050514, -8})); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{9'000'000'001}, + USD(10'000), + IOUAmount{9'486'832'98050514, -8})); + }, + std::nullopt, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); // Single withdrawal by tokens 10000. testAMM([&](AMM& ammAlice, Env&) { @@ -2233,20 +2370,31 @@ private: }); // Single deposit/withdraw by the same account - testAMM([&](AMM& ammAlice, Env&) { - // Since a smaller amount might be deposited due to - // the lp tokens adjustment, withdrawing by tokens - // is generally preferred to withdrawing by amount. - auto lpTokens = ammAlice.deposit(carol, USD(1'000)); - ammAlice.withdraw(carol, lpTokens, USD(0)); - lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6)); - ammAlice.withdraw(carol, lpTokens, USD(0)); - lpTokens = ammAlice.deposit(carol, XRPAmount(1)); - ammAlice.withdraw(carol, lpTokens, XRPAmount(0)); - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + // Since a smaller amount might be deposited due to + // the lp tokens adjustment, withdrawing by tokens + // is generally preferred to withdrawing by amount. + auto lpTokens = ammAlice.deposit(carol, USD(1'000)); + ammAlice.withdraw(carol, lpTokens, USD(0)); + lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6)); + ammAlice.withdraw(carol, lpTokens, USD(0)); + lpTokens = ammAlice.deposit(carol, XRPAmount(1)); + ammAlice.withdraw(carol, lpTokens, XRPAmount(0)); + if (!env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), USD(10'000), ammAlice.tokens())); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(10'000'000'001), + USD(10'000), + ammAlice.tokens())); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + }, + std::nullopt, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); // Single deposit by different accounts and then withdraw // in reverse. @@ -2289,27 +2437,28 @@ private: IOUAmount{10'000'000, 0})); }); - auto const all = supported_amendments(); // Withdraw with EPrice limit. testAMM( [&](AMM& ammAlice, Env& env) { ammAlice.deposit(carol, 1'000'000); ammAlice.withdraw( carol, USD(100), std::nullopt, IOUAmount{520, 0}); - BEAST_EXPECT( - ammAlice.expectBalances( + BEAST_EXPECT(ammAlice.expectLPTokens( + carol, IOUAmount{153'846'15384616, -8})); + if (!env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( XRPAmount(11'000'000'000), STAmount{USD, UINT64_C(9'372'781065088769), -12}, - IOUAmount{10'153'846'15384616, -8}) && - ammAlice.expectLPTokens( - carol, IOUAmount{153'846'15384616, -8})); + IOUAmount{10'153'846'15384616, -8})); + else if (env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(11'000'000'000), + STAmount{USD, UINT64_C(9'372'78106508877), -11}, + IOUAmount{10'153'846'15384616, -8})); ammAlice.withdrawAll(carol); BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); }, - std::nullopt, - 0, - std::nullopt, - {all}); + {.features = {all, all - fixAMMv1_3}, .noLog = true}); // Withdraw with EPrice limit. AssetOut is 0. testAMM( @@ -2317,18 +2466,23 @@ private: ammAlice.deposit(carol, 1'000'000); ammAlice.withdraw( carol, USD(0), std::nullopt, IOUAmount{520, 0}); - BEAST_EXPECT( - ammAlice.expectBalances( - XRPAmount(11'000'000'000), + BEAST_EXPECT(ammAlice.expectLPTokens( + carol, IOUAmount{153'846'15384616, -8})); + if (!env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), STAmount{USD, UINT64_C(9'372'781065088769), -12}, - IOUAmount{10'153'846'15384616, -8}) && - ammAlice.expectLPTokens( - carol, IOUAmount{153'846'15384616, -8})); + IOUAmount{10'153'846'15384616, -8})); + else if (env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), + STAmount{USD, UINT64_C(9'372'78106508877), -11}, + IOUAmount{10'153'846'15384616, -8})); }, std::nullopt, 0, std::nullopt, - {all}); + {all, all - fixAMMv1_3}); // IOU to IOU + transfer fee { @@ -2367,14 +2521,25 @@ private: STAmount{USD, UINT64_C(9'999'999999), -6}, IOUAmount{9'999'999'999, -3})); }); - testAMM([&](AMM& ammAlice, Env&) { - // Single XRP pool - ammAlice.withdraw(alice, std::nullopt, XRPAmount{1}); - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{9'999'999'999}, - USD(10'000), - IOUAmount{9'999'999'9995, -4})); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + // Single XRP pool + ammAlice.withdraw(alice, std::nullopt, XRPAmount{1}); + if (!env.enabled(fixAMMv1_3)) + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{9'999'999'999}, + USD(10'000), + IOUAmount{9'999'999'9995, -4})); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), + USD(10'000), + IOUAmount{9'999'999'9995, -4})); + }, + std::nullopt, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); testAMM([&](AMM& ammAlice, Env&) { // Single USD pool ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10}); @@ -2492,6 +2657,7 @@ private: { testcase("Fee Vote"); using namespace jtx; + auto const all = supported_amendments(); // One vote sets fee to 1%. testAMM([&](AMM& ammAlice, Env& env) { @@ -2509,6 +2675,12 @@ private: std::uint32_t tokens = 10'000'000, std::vector* accounts = nullptr) { Account a(std::to_string(i)); + // post-amendment the amount to deposit is slightly higher + // in order to ensure AMM invariant sqrt(asset1 * asset2) >= tokens + // fund just one USD higher in this case, which is enough for + // deposit to succeed + if (env.enabled(fixAMMv1_3)) + ++fundUSD; fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct); ammAlice.deposit(a, tokens); ammAlice.vote(a, 50 * (i + 1)); @@ -2517,11 +2689,16 @@ private: }; // Eight votes fill all voting slots, set fee 0.175%. - testAMM([&](AMM& ammAlice, Env& env) { - for (int i = 0; i < 7; ++i) - vote(ammAlice, env, i, 10'000); - BEAST_EXPECT(ammAlice.expectTradingFee(175)); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + for (int i = 0; i < 7; ++i) + vote(ammAlice, env, i, 10'000); + BEAST_EXPECT(ammAlice.expectTradingFee(175)); + }, + std::nullopt, + 0, + std::nullopt, + {all}); // Eight votes fill all voting slots, set fee 0.175%. // New vote, same account, sets fee 0.225% @@ -2915,8 +3092,14 @@ private: fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct); ammAlice.deposit(bob, 1'000'000); - BEAST_EXPECT(ammAlice.expectBalances( - XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0})); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{12'000'000'001}, + USD(12'000), + IOUAmount{12'000'000, 0})); // Initial state. Pay bidMin. env(ammAlice.bid({.account = carol, .bidMin = 110})).close(); @@ -2948,8 +3131,16 @@ private: BEAST_EXPECT(ammAlice.expectAuctionSlot( 0, std::nullopt, IOUAmount{110})); // ~321.09 tokens burnt on bidding fees. - BEAST_EXPECT(ammAlice.expectBalances( - XRP(12'000), USD(12'000), IOUAmount{11'999'678'91, -2})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(12'000), + USD(12'000), + IOUAmount{11'999'678'91, -2})); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{12'000'000'001}, + USD(12'000), + IOUAmount{11'999'678'91, -2})); }, std::nullopt, 0, @@ -2978,8 +3169,12 @@ private: auto const slotPrice = IOUAmount{5'200}; ammTokens -= slotPrice; BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice)); - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), USD(13'000), ammTokens)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(13'000), USD(13'000), ammTokens)); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, USD(13'000), ammTokens)); // Discounted trade for (int i = 0; i < 10; ++i) { @@ -3001,11 +3196,16 @@ private: env.balance(ed, USD) == STAmount(USD, UINT64_C(18'999'0057261184), -10)); // USD pool is slightly higher because of the fees. - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), - STAmount(USD, UINT64_C(13'002'98282151422), -11), - ammTokens)); - + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(13'000), + STAmount(USD, UINT64_C(13'002'98282151422), -11), + ammTokens)); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, + STAmount(USD, UINT64_C(13'002'98282151422), -11), + ammTokens)); ammTokens = ammAlice.getLPTokensBalance(); // Trade with the fee for (int i = 0; i < 10; ++i) @@ -3016,48 +3216,81 @@ private: // dan pays ~9.94USD, which is ~10 times more in fees than // carol, bob, ed. the discounted fee is 10 times less // than the trading fee. - BEAST_EXPECT( - env.balance(dan, USD) == - STAmount(USD, UINT64_C(19'490'05672274399), -11)); + + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + env.balance(dan, USD) == + STAmount(USD, UINT64_C(19'490'05672274399), -11)); + else + BEAST_EXPECT( + env.balance(dan, USD) == + STAmount(USD, UINT64_C(19'490'05672274398), -11)); // USD pool gains more in dan's fees. - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), - STAmount{USD, UINT64_C(13'012'92609877023), -11}, - ammTokens)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(13'000), + STAmount{USD, UINT64_C(13'012'92609877023), -11}, + ammTokens)); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, + STAmount{USD, UINT64_C(13'012'92609877024), -11}, + ammTokens)); // Discounted fee payment ammAlice.deposit(carol, USD(100)); ammTokens = ammAlice.getLPTokensBalance(); - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), - STAmount{USD, UINT64_C(13'112'92609877023), -11}, - ammTokens)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(13'000), + STAmount{USD, UINT64_C(13'112'92609877023), -11}, + ammTokens)); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, + STAmount{USD, UINT64_C(13'112'92609877024), -11}, + ammTokens)); env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110))); env.close(); // carol pays 100000 drops in fees // 99900668XRP swapped in for 100USD - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'100'000'668}, - STAmount{USD, UINT64_C(13'012'92609877023), -11}, - ammTokens)); - + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'100'000'668}, + STAmount{USD, UINT64_C(13'012'92609877023), -11}, + ammTokens)); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'100'000'671}, + STAmount{USD, UINT64_C(13'012'92609877024), -11}, + ammTokens)); // Payment with the trading fee env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110))); env.close(); // alice pays ~1.011USD in fees, which is ~10 times more // than carol's fee // 100.099431529USD swapped in for 100XRP - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'668}, - STAmount{USD, UINT64_C(13'114'03663047269), -11}, - ammTokens)); - + if (!features[fixAMMv1_3]) + { + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'668}, + STAmount{USD, UINT64_C(13'114'03663047269), -11}, + ammTokens)); + } + else + { + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'671}, + STAmount{USD, UINT64_C(13'114'03663044937), -11}, + ammTokens)); + } // Auction slot expired, no discounted fee env.close(seconds(TOTAL_TIME_SLOT_SECS + 1)); // clock is parent's based env.close(); - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(29'399'00572620544), -11)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(29'399'00572620544), -11)); ammTokens = ammAlice.getLPTokensBalance(); for (int i = 0; i < 10; ++i) { @@ -3066,23 +3299,45 @@ private: } // carol pays ~9.94USD in fees, which is ~10 times more in // trading fees vs discounted fee. - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(29'389'06197177124), -11)); - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'668}, - STAmount{USD, UINT64_C(13'123'98038490689), -11}, - ammTokens)); - + if (!features[fixAMMv1_3]) + { + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(29'389'06197177124), -11)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'668}, + STAmount{USD, UINT64_C(13'123'98038490689), -11}, + ammTokens)); + } + else + { + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(29'389'06197177129), -11)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'671}, + STAmount{USD, UINT64_C(13'123'98038488352), -11}, + ammTokens)); + } env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110))); env.close(); // carol pays ~1.008XRP in trading fee, which is // ~10 times more than the discounted fee. // 99.815876XRP is swapped in for 100USD - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(13'100'824'790), - STAmount{USD, UINT64_C(13'023'98038490689), -11}, - ammTokens)); + if (!features[fixAMMv1_3]) + { + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(13'100'824'790), + STAmount{USD, UINT64_C(13'023'98038490689), -11}, + ammTokens)); + } + else + { + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(13'100'824'793), + STAmount{USD, UINT64_C(13'023'98038488352), -11}, + ammTokens)); + } }, std::nullopt, 1'000, @@ -4187,8 +4442,9 @@ private: // Offer crossing with AMM LPTokens and XRP. testAMM([&](AMM& ammAlice, Env& env) { + auto const baseFee = env.current()->fees().base.drops(); auto const token1 = ammAlice.lptIssue(); - auto priceXRP = withdrawByTokens( + auto priceXRP = ammAssetOut( STAmount{XRPAmount{10'000'000'000}}, STAmount{token1, 10'000'000}, STAmount{token1, 5'000'000}, @@ -4212,8 +4468,10 @@ private: env(ammAlice.bid({.account = carol, .bidMin = 100})); BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900})); BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100})); - BEAST_EXPECT(accountBalance(env, carol) == "22499999960"); - priceXRP = withdrawByTokens( + BEAST_EXPECT( + accountBalance(env, carol) == + std::to_string(22500000000 - 4 * baseFee)); + priceXRP = ammAssetOut( STAmount{XRPAmount{10'000'000'000}}, STAmount{token1, 9'999'900}, STAmount{token1, 4'999'900}, @@ -4561,7 +4819,10 @@ private: carol, USD(100), std::nullopt, IOUAmount{520, 0}); // carol withdraws ~1,443.44USD auto const balanceAfterWithdraw = [&]() { - return STAmount(USD, UINT64_C(30'443'43891402714), -11); + if (!features[fixAMMv1_3]) + return STAmount(USD, UINT64_C(30'443'43891402714), -11); + else + return STAmount(USD, UINT64_C(30'443'43891402713), -11); }(); BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw); // Set to original pool size @@ -4571,12 +4832,22 @@ private: ammAlice.vote(alice, 0); BEAST_EXPECT(ammAlice.expectTradingFee(0)); auto const tokensNoFee = ammAlice.withdraw(carol, deposit); - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(30'443'43891402716), -11)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(30'443'43891402716), -11)); + else + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(30'443'43891402713), -11)); // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee // LPTokens - BEAST_EXPECT(tokensNoFee == IOUAmount(746'579'80779912, -8)); + if (!features[fixAMMv1_3]) + BEAST_EXPECT( + tokensNoFee == IOUAmount(746'579'80779912, -8)); + else + BEAST_EXPECT( + tokensNoFee == IOUAmount(746'579'80779911, -8)); BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8)); }, std::nullopt, @@ -4838,22 +5109,40 @@ private: // Due to round off some accounts have a tiny gain, while // other have a tiny loss. The last account to withdraw // gets everything in the pool. - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), USD(10'000), IOUAmount{10'000'000})); + if (features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), + STAmount{USD, UINT64_C(10'000'0000000003), -10}, + IOUAmount{10'000'000})); + else + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), USD(10'000), IOUAmount{10'000'000})); BEAST_EXPECT(expectLine(env, ben, USD(1'500'000))); BEAST_EXPECT(expectLine(env, simon, USD(1'500'000))); BEAST_EXPECT(expectLine(env, chris, USD(1'500'000))); BEAST_EXPECT(expectLine(env, dan, USD(1'500'000))); - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + else + BEAST_EXPECT(expectLine(env, carol, USD(30'000))); BEAST_EXPECT(expectLine(env, ed, USD(1'500'000))); BEAST_EXPECT(expectLine(env, paul, USD(1'500'000))); - BEAST_EXPECT(expectLine( - env, - nataly, - STAmount{USD, UINT64_C(1'500'000'000000005), -9})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(expectLine( + env, + nataly, + STAmount{USD, UINT64_C(1'500'000'000000005), -9})); + else + BEAST_EXPECT(expectLine(env, nataly, USD(1'500'000))); ammAlice.withdrawAll(alice); BEAST_EXPECT(!ammAlice.ammExists()); - BEAST_EXPECT(expectLine(env, alice, USD(30'000))); + if (features[fixAMMv1_3]) + BEAST_EXPECT(expectLine( + env, + alice, + STAmount{USD, UINT64_C(30'000'0000000003), -10})); + else + BEAST_EXPECT(expectLine(env, alice, USD(30'000))); // alice XRP balance is 30,000initial - 50 ammcreate fee - // 10drops fee BEAST_EXPECT(accountBalance(env, alice) == "29949999990"); @@ -4864,62 +5153,110 @@ private: {features}); // Same as above but deposit/withdraw in XRP - testAMM([&](AMM& ammAlice, Env& env) { - Account const bob("bob"); - Account const ed("ed"); - Account const paul("paul"); - Account const dan("dan"); - Account const chris("chris"); - Account const simon("simon"); - Account const ben("ben"); - Account const nataly("nataly"); - fund( - env, - gw, - {bob, ed, paul, dan, chris, simon, ben, nataly}, - XRP(2'000'000), - {}, - Fund::Acct); - for (int i = 0; i < 10; ++i) - { - ammAlice.deposit(ben, XRPAmount{1}); - ammAlice.withdrawAll(ben, XRP(0)); - ammAlice.deposit(simon, XRPAmount(1'000)); - ammAlice.withdrawAll(simon, XRP(0)); - ammAlice.deposit(chris, XRP(1)); - ammAlice.withdrawAll(chris, XRP(0)); - ammAlice.deposit(dan, XRP(10)); - ammAlice.withdrawAll(dan, XRP(0)); - ammAlice.deposit(bob, XRP(100)); - ammAlice.withdrawAll(bob, XRP(0)); - ammAlice.deposit(carol, XRP(1'000)); - ammAlice.withdrawAll(carol, XRP(0)); - ammAlice.deposit(ed, XRP(10'000)); - ammAlice.withdrawAll(ed, XRP(0)); - ammAlice.deposit(paul, XRP(100'000)); - ammAlice.withdrawAll(paul, XRP(0)); - ammAlice.deposit(nataly, XRP(1'000'000)); - ammAlice.withdrawAll(nataly, XRP(0)); - } - // No round off with XRP in this test - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), USD(10'000), IOUAmount{10'000'000})); - ammAlice.withdrawAll(alice); - BEAST_EXPECT(!ammAlice.ammExists()); - // 20,000 initial - (deposit+withdraw) * 10 - auto const xrpBalance = (XRP(2'000'000) - txfee(env, 20)).getText(); - BEAST_EXPECT(accountBalance(env, ben) == xrpBalance); - BEAST_EXPECT(accountBalance(env, simon) == xrpBalance); - BEAST_EXPECT(accountBalance(env, chris) == xrpBalance); - BEAST_EXPECT(accountBalance(env, dan) == xrpBalance); - // 30,000 initial - (deposit+withdraw) * 10 - BEAST_EXPECT(accountBalance(env, carol) == "29999999800"); - BEAST_EXPECT(accountBalance(env, ed) == xrpBalance); - BEAST_EXPECT(accountBalance(env, paul) == xrpBalance); - BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance); - // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee - BEAST_EXPECT(accountBalance(env, alice) == "29949999990"); - }); + testAMM( + [&](AMM& ammAlice, Env& env) { + Account const bob("bob"); + Account const ed("ed"); + Account const paul("paul"); + Account const dan("dan"); + Account const chris("chris"); + Account const simon("simon"); + Account const ben("ben"); + Account const nataly("nataly"); + fund( + env, + gw, + {bob, ed, paul, dan, chris, simon, ben, nataly}, + XRP(2'000'000), + {}, + Fund::Acct); + for (int i = 0; i < 10; ++i) + { + ammAlice.deposit(ben, XRPAmount{1}); + ammAlice.withdrawAll(ben, XRP(0)); + ammAlice.deposit(simon, XRPAmount(1'000)); + ammAlice.withdrawAll(simon, XRP(0)); + ammAlice.deposit(chris, XRP(1)); + ammAlice.withdrawAll(chris, XRP(0)); + ammAlice.deposit(dan, XRP(10)); + ammAlice.withdrawAll(dan, XRP(0)); + ammAlice.deposit(bob, XRP(100)); + ammAlice.withdrawAll(bob, XRP(0)); + ammAlice.deposit(carol, XRP(1'000)); + ammAlice.withdrawAll(carol, XRP(0)); + ammAlice.deposit(ed, XRP(10'000)); + ammAlice.withdrawAll(ed, XRP(0)); + ammAlice.deposit(paul, XRP(100'000)); + ammAlice.withdrawAll(paul, XRP(0)); + ammAlice.deposit(nataly, XRP(1'000'000)); + ammAlice.withdrawAll(nataly, XRP(0)); + } + auto const baseFee = env.current()->fees().base.drops(); + if (!features[fixAMMv1_3]) + { + // No round off with XRP in this test + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), USD(10'000), IOUAmount{10'000'000})); + ammAlice.withdrawAll(alice); + BEAST_EXPECT(!ammAlice.ammExists()); + // 20,000 initial - (deposit+withdraw) * 10 + auto const xrpBalance = + (XRP(2'000'000) - txfee(env, 20)).getText(); + BEAST_EXPECT(accountBalance(env, ben) == xrpBalance); + BEAST_EXPECT(accountBalance(env, simon) == xrpBalance); + BEAST_EXPECT(accountBalance(env, chris) == xrpBalance); + BEAST_EXPECT(accountBalance(env, dan) == xrpBalance); + + // 30,000 initial - (deposit+withdraw) * 10 + BEAST_EXPECT( + accountBalance(env, carol) == + std::to_string(30'000'000'000 - 20 * baseFee)); + BEAST_EXPECT(accountBalance(env, ed) == xrpBalance); + BEAST_EXPECT(accountBalance(env, paul) == xrpBalance); + BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance); + // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee + BEAST_EXPECT( + accountBalance(env, alice) == + std::to_string(29'950'000'000 - baseFee)); + } + else + { + // post-amendment the rounding takes place to ensure + // AMM invariant + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(10'000'000'080), + USD(10'000), + IOUAmount{10'000'000})); + ammAlice.withdrawAll(alice); + BEAST_EXPECT(!ammAlice.ammExists()); + auto const xrpBalance = + XRP(2'000'000) - txfee(env, 20) - drops(10); + auto const xrpBalanceText = xrpBalance.getText(); + BEAST_EXPECT(accountBalance(env, ben) == xrpBalanceText); + BEAST_EXPECT(accountBalance(env, simon) == xrpBalanceText); + BEAST_EXPECT(accountBalance(env, chris) == xrpBalanceText); + BEAST_EXPECT(accountBalance(env, dan) == xrpBalanceText); + BEAST_EXPECT( + accountBalance(env, carol) == + std::to_string(30'000'000'000 - 20 * baseFee - 10)); + BEAST_EXPECT( + accountBalance(env, ed) == + (xrpBalance + drops(2)).getText()); + BEAST_EXPECT( + accountBalance(env, paul) == + (xrpBalance + drops(3)).getText()); + BEAST_EXPECT( + accountBalance(env, nataly) == + (xrpBalance + drops(5)).getText()); + BEAST_EXPECT( + accountBalance(env, alice) == + std::to_string(29'950'000'000 - baseFee + 80)); + } + }, + std::nullopt, + 0, + std::nullopt, + {features}); } void @@ -5804,11 +6141,11 @@ private: } void - testFixOverflowOffer(FeatureBitset features) + testFixOverflowOffer(FeatureBitset featuresInitial) { using namespace jtx; using namespace std::chrono; - FeatureBitset const all{features}; + FeatureBitset const all{featuresInitial}; Account const gatehub{"gatehub"}; Account const bitstamp{"bitstamp"}; @@ -5833,6 +6170,7 @@ private: STAmount const goodUsdBIT; STAmount const goodUsdBITr; IOUAmount const lpTokenBalance; + std::optional const lpTokenBalanceAlt = {}; double const offer1BtcGH = 0.1; double const offer2BtcGH = 0.1; double const offer2UsdGH = 1; @@ -5858,6 +6196,7 @@ private: .goodUsdBIT{usdBIT, uint64_t(8'464739069120721), -15}, // .goodUsdBITr{usdBIT, uint64_t(8'464739069098152), -15}, // .lpTokenBalance = {28'61817604250837, -14}, // + .lpTokenBalanceAlt = IOUAmount{28'61817604250836, -14}, // .offer1BtcGH = 0.1, // .offer2BtcGH = 0.1, // .offer2UsdGH = 1, // @@ -6035,72 +6374,91 @@ private: }) { testcase(input.testCase); + for (auto const& features : {all - fixAMMv1_3, all}) + { + // Env env(*this, features, + // std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); - Env env( - *this, envconfig(), all, nullptr, beast::severities::kDisabled); + env.fund(XRP(5'000), gatehub, bitstamp, trader); + env.close(); - env.fund(XRP(5'000), gatehub, bitstamp, trader); - env.close(); + if (input.rateGH != 0.0) + env(rate(gatehub, input.rateGH)); + if (input.rateBIT != 0.0) + env(rate(bitstamp, input.rateBIT)); - if (input.rateGH != 0.0) - env(rate(gatehub, input.rateGH)); - if (input.rateBIT != 0.0) - env(rate(bitstamp, input.rateBIT)); + env(trust(trader, usdGH(10'000'000))); + env(trust(trader, usdBIT(10'000'000))); + env(trust(trader, btcGH(10'000'000))); + env.close(); - env(trust(trader, usdGH(10'000'000))); - env(trust(trader, usdBIT(10'000'000))); - env(trust(trader, btcGH(10'000'000))); - env.close(); + env(pay(gatehub, trader, usdGH(100'000))); + env(pay(gatehub, trader, btcGH(100'000))); + env(pay(bitstamp, trader, usdBIT(100'000))); + env.close(); - env(pay(gatehub, trader, usdGH(100'000))); - env(pay(gatehub, trader, btcGH(100'000))); - env(pay(bitstamp, trader, usdBIT(100'000))); - env.close(); + AMM amm{ + env, + trader, + usdGH(input.poolUsdGH), + usdBIT(input.poolUsdBIT)}; + env.close(); - AMM amm{ - env, trader, usdGH(input.poolUsdGH), usdBIT(input.poolUsdBIT)}; - env.close(); + IOUAmount const preSwapLPTokenBalance = + amm.getLPTokensBalance(); - IOUAmount const preSwapLPTokenBalance = amm.getLPTokensBalance(); + env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH))); + env(offer( + trader, + btcGH(input.offer2BtcGH), + usdGH(input.offer2UsdGH))); + env.close(); - env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH))); - env(offer( - trader, btcGH(input.offer2BtcGH), usdGH(input.offer2UsdGH))); - env.close(); + env(pay(trader, trader, input.sendUsdGH), + path(~usdGH), + path(~btcGH, ~usdGH), + sendmax(input.sendMaxUsdBIT), + txflags(tfPartialPayment)); + env.close(); - env(pay(trader, trader, input.sendUsdGH), - path(~usdGH), - path(~btcGH, ~usdGH), - sendmax(input.sendMaxUsdBIT), - txflags(tfPartialPayment)); - env.close(); + auto const failUsdGH = input.failUsdGHr; + auto const failUsdBIT = input.failUsdBITr; + auto const goodUsdGH = input.goodUsdGHr; + auto const goodUsdBIT = input.goodUsdBITr; - auto const failUsdGH = input.failUsdGHr; - auto const failUsdBIT = input.failUsdBITr; - auto const goodUsdGH = input.goodUsdGHr; - auto const goodUsdBIT = input.goodUsdBITr; + auto const lpTokenBalance = + env.enabled(fixAMMv1_3) && input.lpTokenBalanceAlt + ? *input.lpTokenBalanceAlt + : input.lpTokenBalance; - BEAST_EXPECT(amm.expectBalances( - goodUsdGH, goodUsdBIT, input.lpTokenBalance)); + BEAST_EXPECT( + amm.expectBalances(goodUsdGH, goodUsdBIT, lpTokenBalance)); - // Invariant: LPToken balance must not change in a - // payment or a swap transaction - BEAST_EXPECT(amm.getLPTokensBalance() == preSwapLPTokenBalance); + // Invariant: LPToken balance must not change in a + // payment or a swap transaction + BEAST_EXPECT(amm.getLPTokensBalance() == preSwapLPTokenBalance); - // Invariant: The square root of (product of the pool - // balances) must be at least the LPTokenBalance - Number const sqrtPoolProduct = root2(goodUsdGH * goodUsdBIT); + // Invariant: The square root of (product of the pool + // balances) must be at least the LPTokenBalance + Number const sqrtPoolProduct = root2(goodUsdGH * goodUsdBIT); - // Include a tiny tolerance for the test cases using - // .goodUsdGH{usdGH, uint64_t(35'44113971506987), - // -14}, .goodUsdBIT{usdBIT, - // uint64_t(2'821579689703915), -15}, - // These two values multiply - // to 99.99999999999994227040383754105 which gets - // internally rounded to 100, due to representation - // error. - BEAST_EXPECT( - (sqrtPoolProduct + Number{1, -14} >= input.lpTokenBalance)); + // Include a tiny tolerance for the test cases using + // .goodUsdGH{usdGH, uint64_t(35'44113971506987), + // -14}, .goodUsdBIT{usdBIT, + // uint64_t(2'821579689703915), -15}, + // These two values multiply + // to 99.99999999999994227040383754105 which gets + // internally rounded to 100, due to representation + // error. + BEAST_EXPECT( + (sqrtPoolProduct + Number{1, -14} >= input.lpTokenBalance)); + } } } @@ -6246,11 +6604,19 @@ private: void testLPTokenBalance(FeatureBitset features) { + testcase("LPToken Balance"); using namespace jtx; // Last Liquidity Provider is the issuer of one token { - Env env(*this, features); + std::string logs; + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); fund( env, gw, @@ -6261,7 +6627,9 @@ private: amm.deposit(alice, IOUAmount{1'876123487565916, -15}); amm.deposit(carol, IOUAmount{1'000'000}); amm.withdrawAll(alice); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{0})); amm.withdrawAll(carol); + BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount{0})); auto const lpToken = getAccountLines( env, gw, amm.lptIssue())[jss::lines][0u][jss::balance]; auto const lpTokenBalance = @@ -6522,6 +6890,430 @@ private: }); } + // void + // testFailedPseudoAccount() + // { + // using namespace test::jtx; + + // auto const testCase = [&](std::string suffix, + // FeatureBitset features) { + // testcase("Failed pseudo-account allocation " + suffix); + // std::string logs; + // Env env{ + // *this, features, std::make_unique(&logs)}; + // env.fund(XRP(30'000), gw, alice); + // env.close(); + // env(trust(alice, gw["USD"](30'000), 0)); + // env(pay(gw, alice, USD(10'000))); + // env.close(); + + // STAmount amount = XRP(10'000); + // STAmount amount2 = USD(10'000); + // auto const keylet = + // keylet::amm(amount.issue(), amount2.issue()); + // for (int i = 0; i < 256; ++i) + // { + // AccountID const accountId = + // ripple::pseudoAccountAddress( + // *env.current(), keylet.key); + + // env(pay(env.master.id(), accountId, XRP(1000)), + // seq(autofill), + // fee(autofill), + // sig(autofill)); + // } + + // AMM ammAlice( + // env, + // alice, + // amount, + // amount2, + // features[featureSingleAssetVault] + // ? ter{terADDRESS_COLLISION} + // : ter{tecDUPLICATE}); + // }; + + // testCase( + // "tecDUPLICATE", + // supported_amendments() - featureSingleAssetVault); + // testCase( + // "terADDRESS_COLLISION", + // supported_amendments() | featureSingleAssetVault); + // } + + void + testDepositAndWithdrawRounding(FeatureBitset features) + { + testcase("Deposit and Withdraw Rounding V2"); + using namespace jtx; + + auto const XPM = gw["XPM"]; + STAmount xrpBalance{XRPAmount(692'614'492'126)}; + STAmount xpmBalance{XPM, UINT64_C(18'610'359'80246901), -8}; + STAmount amount{XPM, UINT64_C(6'566'496939465400), -12}; + std::uint16_t tfee = 941; + + auto test = [&](auto&& cb, std::uint16_t tfee_) { + Env env(*this, features); + env.fund(XRP(1'000'000), gw); + env.fund(XRP(1'000), alice); + env(trust(alice, XPM(7'000))); + env(pay(gw, alice, amount)); + + AMM amm(env, gw, xrpBalance, xpmBalance, CreateArg{.tfee = tfee_}); + // AMM LPToken balance required to replicate single deposit + // failure + STAmount lptAMMBalance{ + amm.lptIssue(), UINT64_C(3'234'987'266'485968), -6}; + auto const burn = + IOUAmount{amm.getLPTokensBalance() - lptAMMBalance}; + // burn tokens to get to the required AMM state + env(amm.bid(BidArg{.account = gw, .bidMin = burn, .bidMax = burn})); + cb(amm, env); + }; + test( + [&](AMM& amm, Env& env) { + auto const err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) + : ter(tecUNFUNDED_AMM); + amm.deposit(DepositArg{ + .account = alice, .asset1In = amount, .err = err}); + }, + tfee); + test( + [&](AMM& amm, Env& env) { + auto const [amount, amount2, lptAMM] = amm.balances(XRP, XPM); + auto const withdraw = STAmount{XPM, 1, -5}; + amm.withdraw(WithdrawArg{.asset1Out = STAmount{XPM, 1, -5}}); + auto const [amount_, amount2_, lptAMM_] = + amm.balances(XRP, XPM); + if (!env.enabled(fixAMMv1_3)) + BEAST_EXPECT((amount2 - amount2_) > withdraw); + else + BEAST_EXPECT((amount2 - amount2_) <= withdraw); + }, + 0); + } + + void + invariant( + jtx::AMM& amm, + jtx::Env& env, + std::string const& msg, + bool shouldFail) + { + auto const [amount, amount2, lptBalance] = amm.balances(GBP, EUR); + + NumberRoundModeGuard g( + env.enabled(fixAMMv1_3) ? Number::upward : Number::getround()); + auto const res = root2(amount * amount2); + + if (shouldFail) + BEAST_EXPECT(res < lptBalance); + else + BEAST_EXPECT(res >= lptBalance); + } + + void + testDepositRounding(FeatureBitset all) + { + testcase("Deposit Rounding"); + using namespace jtx; + + // Single asset deposit + for (auto const& deposit : + {STAmount(EUR, 1, 1), + STAmount(EUR, 1, 2), + STAmount(EUR, 1, 5), + STAmount(EUR, 1, -3), // fail + STAmount(EUR, 1, -6), + STAmount(EUR, 1, -9)}) + { + testAMM( + [&](AMM& ammAlice, Env& env) { + fund( + env, + gw, + {bob}, + XRP(10'000'000), + {GBP(100'000), EUR(100'000)}, + Fund::Acct); + env.close(); + + ammAlice.deposit( + DepositArg{.account = bob, .asset1In = deposit}); + invariant( + ammAlice, + env, + "dep1", + deposit == STAmount{EUR, 1, -3} && + !env.enabled(fixAMMv1_3)); + }, + {{GBP(30'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + } + + // Two-asset proportional deposit (1:1 pool ratio) + testAMM( + [&](AMM& ammAlice, Env& env) { + fund( + env, + gw, + {bob}, + XRP(10'000'000), + {GBP(100'000), EUR(100'000)}, + Fund::Acct); + env.close(); + + STAmount const depositEuro{ + EUR, UINT64_C(10'1234567890123456), -16}; + STAmount const depositGBP{ + GBP, UINT64_C(10'1234567890123456), -16}; + + ammAlice.deposit(DepositArg{ + .account = bob, + .asset1In = depositEuro, + .asset2In = depositGBP}); + invariant(ammAlice, env, "dep2", false); + }, + {{GBP(30'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // Two-asset proportional deposit (1:3 pool ratio) + for (auto const& exponent : {1, 2, 3, 4, -3 /*fail*/, -6, -9}) + { + testAMM( + [&](AMM& ammAlice, Env& env) { + fund( + env, + gw, + {bob}, + XRP(10'000'000), + {GBP(100'000), EUR(100'000)}, + Fund::Acct); + env.close(); + + STAmount const depositEuro{EUR, 1, exponent}; + STAmount const depositGBP{GBP, 1, exponent}; + + ammAlice.deposit(DepositArg{ + .account = bob, + .asset1In = depositEuro, + .asset2In = depositGBP}); + invariant( + ammAlice, + env, + "dep3", + exponent != -3 && !env.enabled(fixAMMv1_3)); + }, + {{GBP(10'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + } + + // tfLPToken deposit + testAMM( + [&](AMM& ammAlice, Env& env) { + fund( + env, + gw, + {bob}, + XRP(10'000'000), + {GBP(100'000), EUR(100'000)}, + Fund::Acct); + env.close(); + + ammAlice.deposit(DepositArg{ + .account = bob, + .tokens = IOUAmount{10'1234567890123456, -16}}); + invariant(ammAlice, env, "dep4", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // tfOneAssetLPToken deposit + for (auto const& tokens : + {IOUAmount{1, -3}, + IOUAmount{1, -2}, + IOUAmount{1, -1}, + IOUAmount{1}, + IOUAmount{10}, + IOUAmount{100}, + IOUAmount{1'000}, + IOUAmount{10'000}}) + { + testAMM( + [&](AMM& ammAlice, Env& env) { + fund( + env, + gw, + {bob}, + XRP(10'000'000), + {GBP(100'000), EUR(1'000'000)}, + Fund::Acct); + env.close(); + + ammAlice.deposit(DepositArg{ + .account = bob, + .tokens = tokens, + .asset1In = STAmount{EUR, 1, 6}}); + invariant(ammAlice, env, "dep5", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + } + + // Single deposit with EP not exceeding specified: + // 1'000 GBP with EP not to exceed 5 (GBP/TokensOut) + testAMM( + [&](AMM& ammAlice, Env& env) { + fund( + env, + gw, + {bob}, + XRP(10'000'000), + {GBP(100'000), EUR(100'000)}, + Fund::Acct); + env.close(); + + ammAlice.deposit( + bob, GBP(1'000), std::nullopt, STAmount{GBP, 5}); + invariant(ammAlice, env, "dep6", false); + }, + {{GBP(30'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + } + + void + testWithdrawRounding(FeatureBitset all) + { + testcase("Withdraw Rounding"); + + using namespace jtx; + + // tfLPToken mode + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdraw(alice, 1'000); + invariant(ammAlice, env, "with1", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // tfWithdrawAll mode + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdraw( + WithdrawArg{.account = alice, .flags = tfWithdrawAll}); + invariant(ammAlice, env, "with2", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // tfTwoAsset withdraw mode + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdraw(WithdrawArg{ + .account = alice, + .asset1Out = STAmount{GBP, 3'500}, + .asset2Out = STAmount{EUR, 15'000}, + .flags = tfTwoAsset}); + invariant(ammAlice, env, "with3", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // tfSingleAsset withdraw mode + // Note: This test fails with 0 trading fees, but doesn't fail + // if trading fees is set to 1'000 -- I suspect the compound + // operations in AMMHelpers.cpp:withdrawByTokens compensate for + // the rounding errors + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdraw(WithdrawArg{ + .account = alice, + .asset1Out = STAmount{GBP, 1'234}, + .flags = tfSingleAsset}); + invariant(ammAlice, env, "with4", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // tfOneAssetWithdrawAll mode + testAMM( + [&](AMM& ammAlice, Env& env) { + fund( + env, + gw, + {bob}, + XRP(10'000'000), + {GBP(100'000), EUR(100'000)}, + Fund::Acct); + env.close(); + + ammAlice.deposit(DepositArg{ + .account = bob, .asset1In = STAmount{GBP, 3'456}}); + + ammAlice.withdraw(WithdrawArg{ + .account = bob, + .asset1Out = STAmount{GBP, 1'000}, + .flags = tfOneAssetWithdrawAll}); + invariant(ammAlice, env, "with5", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // tfOneAssetLPToken mode + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdraw(WithdrawArg{ + .account = alice, + .tokens = 1'000, + .asset1Out = STAmount{GBP, 100}, + .flags = tfOneAssetLPToken}); + invariant(ammAlice, env, "with6", false); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + + // tfLimitLPToken mode + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdraw(WithdrawArg{ + .account = alice, + .asset1Out = STAmount{GBP, 100}, + .maxEP = IOUAmount{2}, + .flags = tfLimitLPToken}); + invariant(ammAlice, env, "with7", true); + }, + {{GBP(7'000), EUR(30'000)}}, + 0, + std::nullopt, + {all}); + } + void run() override { @@ -6537,32 +7329,52 @@ private: testFeeVote(); testInvalidBid(); testBid(all); + testBid(all - fixAMMv1_3); testInvalidAMMPayment(); testBasicPaymentEngine(all); + testBasicPaymentEngine(all - fixAMMv1_3); testBasicPaymentEngine(all - fixReducedOffersV2); + testBasicPaymentEngine(all - fixAMMv1_3 - fixReducedOffersV2); testAMMTokens(); testAmendment(); testFlags(); testRippling(); testAMMAndCLOB(all); + testAMMAndCLOB(all - fixAMMv1_3); testTradingFee(all); + testTradingFee(all - fixAMMv1_3); testAdjustedTokens(all); + testAdjustedTokens(all - fixAMMv1_3); testAutoDelete(); testClawback(); testAMMID(); testSelection(all); + testSelection(all - fixAMMv1_3); testFixDefaultInnerObj(); testMalformed(); testFixOverflowOffer(all); + testFixOverflowOffer(all - fixAMMv1_3); testSwapRounding(); testFixChangeSpotPriceQuality(all); + testFixChangeSpotPriceQuality(all - fixAMMv1_3); testFixAMMOfferBlockedByLOB(all); + testFixAMMOfferBlockedByLOB(all - fixAMMv1_3); testLPTokenBalance(all); + testLPTokenBalance(all - fixAMMv1_3); testAMMClawback(all); testAMMClawback(all - featureAMMClawback); + testAMMClawback(all - fixAMMv1_3 - featureAMMClawback); testAMMDepositWithFrozenAssets(all); testAMMDepositWithFrozenAssets(all - featureAMMClawback); + testAMMDepositWithFrozenAssets(all - fixAMMv1_3 - featureAMMClawback); testFixReserveCheckOnWithdrawal(all); + testDepositAndWithdrawRounding(all); + testDepositAndWithdrawRounding(all - fixAMMv1_3); + testDepositRounding(all); + testDepositRounding(all - fixAMMv1_3); + testWithdrawRounding(all); + testWithdrawRounding(all - fixAMMv1_3); + // testFailedPseudoAccount(); } }; diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index 52039f74a..9f3a07e8b 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -125,7 +125,6 @@ class AMM STAmount const asset1_; STAmount const asset2_; uint256 const ammID_; - IOUAmount const initialLPTokens_; bool log_; bool doClose_; // Predict next purchase price @@ -138,6 +137,7 @@ class AMM std::uint32_t const fee_; AccountID const ammAccount_; Issue const lptIssue_; + IOUAmount const initialLPTokens_; public: AMM(Env& env, @@ -194,6 +194,12 @@ public: Issue const& issue2, std::optional const& account = std::nullopt) const; + std::tuple + balances(std::optional const& account = std::nullopt) const + { + return balances(asset1_.get(), asset2_.get(), account); + } + [[nodiscard]] bool expectLPTokens(AccountID const& account, IOUAmount const& tokens) const; @@ -428,6 +434,9 @@ private: [[nodiscard]] bool expectAuctionSlot(auto&& cb) const; + + IOUAmount + initialTokens(); }; namespace amm { diff --git a/src/test/jtx/AMMTest.h b/src/test/jtx/AMMTest.h index 0481dc98a..4902e5f49 100644 --- a/src/test/jtx/AMMTest.h +++ b/src/test/jtx/AMMTest.h @@ -33,6 +33,15 @@ class AMM; enum class Fund { All, Acct, Gw, IOUOnly }; +struct TestAMMArg +{ + std::optional> pool = std::nullopt; + std::uint16_t tfee = 0; + std::optional ter = std::nullopt; + std::vector features = {supported_amendments()}; + bool noLog = false; +}; + void fund( jtx::Env& env, @@ -85,6 +94,11 @@ protected: std::uint16_t tfee = 0, std::optional const& ter = std::nullopt, std::vector const& features = {supported_amendments()}); + + void + testAMM( + std::function&& cb, + TestAMMArg const& arg); }; class AMMTest : public jtx::AMMTestBase diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index dc5b4be48..34056d3b0 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -646,6 +646,12 @@ public: void disableFeature(uint256 const feature); + bool + enabled(uint256 feature) const + { + return current()->rules().enabled(feature); + } + private: void fund(bool setDefaultRipple, STAmount const& amount, Account const& account); diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index 089d3508d..cbb815994 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -18,8 +18,9 @@ //============================================================================== #include - #include + +#include #include #include #include @@ -38,12 +39,16 @@ number(STAmount const& a) return a; } -static IOUAmount -initialTokens(STAmount const& asset1, STAmount const& asset2) +IOUAmount +AMM::initialTokens() { - auto const product = number(asset1) * number(asset2); - return (IOUAmount)(product.mantissa() >= 0 ? root2(product) - : root2(-product)); + if (!env_.enabled(fixAMMv1_3)) + { + auto const product = number(asset1_) * number(asset2_); + return (IOUAmount)(product.mantissa() >= 0 ? root2(product) + : root2(-product)); + } + return getLPTokensBalance(); } AMM::AMM( @@ -64,7 +69,6 @@ AMM::AMM( , asset1_(asset1) , asset2_(asset2) , ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key) - , initialLPTokens_(initialTokens(asset1, asset2)) , log_(log) , doClose_(close) , lastPurchasePrice_(0) @@ -77,6 +81,7 @@ AMM::AMM( asset1_.issue().currency, asset2_.issue().currency, ammAccount_)) + , initialLPTokens_(initialTokens()) { } diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 7ba567a37..eb80d9ce7 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include - #include +#include +#include #include #include #include @@ -104,15 +104,37 @@ AMMTestBase::testAMM( std::uint16_t tfee, std::optional const& ter, std::vector const& vfeatures) +{ + testAMM( + std::move(cb), + TestAMMArg{ + .pool = pool, .tfee = tfee, .ter = ter, .features = vfeatures}); +} + +void +AMMTestBase::testAMM( + std::function&& cb, + TestAMMArg const& arg) { using namespace jtx; - for (auto const& features : vfeatures) + std::string logs; + + for (auto const& features : arg.features) { - Env env{*this, features}; + // Env env{ + // *this, + // features, + // arg.noLog ? std::make_unique(&logs) : nullptr}; + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); auto const [asset1, asset2] = - pool ? *pool : std::make_pair(XRP(10000), USD(10000)); + arg.pool ? *arg.pool : std::make_pair(XRP(10000), USD(10000)); auto tofund = [&](STAmount const& a) -> STAmount { if (a.native()) { @@ -142,7 +164,7 @@ AMMTestBase::testAMM( alice, asset1, asset2, - CreateArg{.log = false, .tfee = tfee, .err = ter}); + CreateArg{.log = false, .tfee = arg.tfee, .err = arg.ter}); if (BEAST_EXPECT( ammAlice.expectBalances(asset1, asset2, ammAlice.tokens()))) cb(ammAlice, env); diff --git a/src/test/rpc/AMMInfo_test.cpp b/src/test/rpc/AMMInfo_test.cpp index c1e059a3e..98e62d892 100644 --- a/src/test/rpc/AMMInfo_test.cpp +++ b/src/test/rpc/AMMInfo_test.cpp @@ -203,98 +203,119 @@ public: } void - testVoteAndBid() + testVoteAndBid(FeatureBitset features) { testcase("Vote and Bid"); using namespace jtx; - testAMM([&](AMM& ammAlice, Env& env) { - BEAST_EXPECT(ammAlice.expectAmmRpcInfo( - XRP(10000), USD(10000), IOUAmount{10000000, 0})); - std::unordered_map votes; - votes.insert({alice.human(), 0}); - for (int i = 0; i < 7; ++i) - { - Account a(std::to_string(i)); - votes.insert({a.human(), 50 * (i + 1)}); - fund(env, gw, {a}, {USD(10000)}, Fund::Acct); - ammAlice.deposit(a, 10000000); - ammAlice.vote(a, 50 * (i + 1)); - } - BEAST_EXPECT(ammAlice.expectTradingFee(175)); - Account ed("ed"); - Account bill("bill"); - env.fund(XRP(1000), bob, ed, bill); - env(ammAlice.bid( - {.bidMin = 100, .authAccounts = {carol, bob, ed, bill}})); - BEAST_EXPECT(ammAlice.expectAmmRpcInfo( - XRP(80000), - USD(80000), - IOUAmount{79994400}, - std::nullopt, - std::nullopt, - ammAlice.ammAccount())); - for (auto i = 0; i < 2; ++i) - { - std::unordered_set authAccounts = { - carol.human(), bob.human(), ed.human(), bill.human()}; - auto const ammInfo = i ? ammAlice.ammRpcInfo() - : ammAlice.ammRpcInfo( - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - ammAlice.ammAccount()); - auto const& amm = ammInfo[jss::amm]; - try + testAMM( + [&](AMM& ammAlice, Env& env) { + BEAST_EXPECT(ammAlice.expectAmmRpcInfo( + XRP(10000), USD(10000), IOUAmount{10000000, 0})); + std::unordered_map votes; + votes.insert({alice.human(), 0}); + for (int i = 0; i < 7; ++i) { - // votes - auto const voteSlots = amm[jss::vote_slots]; - auto votesCopy = votes; - for (std::uint8_t i = 0; i < 8; ++i) + Account a(std::to_string(i)); + votes.insert({a.human(), 50 * (i + 1)}); + if (!features[fixAMMv1_3]) + fund(env, gw, {a}, {USD(10000)}, Fund::Acct); + else + fund(env, gw, {a}, {USD(10001)}, Fund::Acct); + ammAlice.deposit(a, 10000000); + ammAlice.vote(a, 50 * (i + 1)); + } + BEAST_EXPECT(ammAlice.expectTradingFee(175)); + Account ed("ed"); + Account bill("bill"); + env.fund(XRP(1000), bob, ed, bill); + env(ammAlice.bid( + {.bidMin = 100, .authAccounts = {carol, bob, ed, bill}})); + if (!features[fixAMMv1_3]) + BEAST_EXPECT(ammAlice.expectAmmRpcInfo( + XRP(80000), + USD(80000), + IOUAmount{79994400}, + std::nullopt, + std::nullopt, + ammAlice.ammAccount())); + else + BEAST_EXPECT(ammAlice.expectAmmRpcInfo( + XRPAmount(80000000005), + STAmount{USD, UINT64_C(80'000'00000000005), -11}, + IOUAmount{79994400}, + std::nullopt, + std::nullopt, + ammAlice.ammAccount())); + for (auto i = 0; i < 2; ++i) + { + std::unordered_set authAccounts = { + carol.human(), bob.human(), ed.human(), bill.human()}; + auto const ammInfo = i ? ammAlice.ammRpcInfo() + : ammAlice.ammRpcInfo( + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + ammAlice.ammAccount()); + auto const& amm = ammInfo[jss::amm]; + try { - if (!BEAST_EXPECT( - votes[voteSlots[i][jss::account].asString()] == - voteSlots[i][jss::trading_fee].asUInt() && - voteSlots[i][jss::vote_weight].asUInt() == - 12500)) + // votes + auto const voteSlots = amm[jss::vote_slots]; + auto votesCopy = votes; + for (std::uint8_t i = 0; i < 8; ++i) + { + if (!BEAST_EXPECT( + votes[voteSlots[i][jss::account] + .asString()] == + voteSlots[i][jss::trading_fee] + .asUInt() && + voteSlots[i][jss::vote_weight].asUInt() == + 12500)) + return; + votes.erase(voteSlots[i][jss::account].asString()); + } + if (!BEAST_EXPECT(votes.empty())) return; - votes.erase(voteSlots[i][jss::account].asString()); - } - if (!BEAST_EXPECT(votes.empty())) - return; - votes = votesCopy; + votes = votesCopy; - // bid - auto const auctionSlot = amm[jss::auction_slot]; - for (std::uint8_t i = 0; i < 4; ++i) - { - if (!BEAST_EXPECT(authAccounts.contains( + // bid + auto const auctionSlot = amm[jss::auction_slot]; + for (std::uint8_t i = 0; i < 4; ++i) + { + if (!BEAST_EXPECT(authAccounts.contains( + auctionSlot[jss::auth_accounts][i] + [jss::account] + .asString()))) + return; + authAccounts.erase( auctionSlot[jss::auth_accounts][i][jss::account] - .asString()))) + .asString()); + } + if (!BEAST_EXPECT(authAccounts.empty())) return; - authAccounts.erase( - auctionSlot[jss::auth_accounts][i][jss::account] - .asString()); + BEAST_EXPECT( + auctionSlot[jss::account].asString() == + alice.human() && + auctionSlot[jss::discounted_fee].asUInt() == 17 && + auctionSlot[jss::price][jss::value].asString() == + "5600" && + auctionSlot[jss::price][jss::currency].asString() == + to_string(ammAlice.lptIssue().currency) && + auctionSlot[jss::price][jss::issuer].asString() == + to_string(ammAlice.lptIssue().account)); + } + catch (std::exception const& e) + { + fail(e.what(), __FILE__, __LINE__); } - if (!BEAST_EXPECT(authAccounts.empty())) - return; - BEAST_EXPECT( - auctionSlot[jss::account].asString() == alice.human() && - auctionSlot[jss::discounted_fee].asUInt() == 17 && - auctionSlot[jss::price][jss::value].asString() == - "5600" && - auctionSlot[jss::price][jss::currency].asString() == - to_string(ammAlice.lptIssue().currency) && - auctionSlot[jss::price][jss::issuer].asString() == - to_string(ammAlice.lptIssue().account)); } - catch (std::exception const& e) - { - fail(e.what(), __FILE__, __LINE__); - } - } - }); + }, + std::nullopt, + 0, + std::nullopt, + {features}); } void @@ -337,9 +358,12 @@ public: void run() override { + using namespace jtx; + auto const all = supported_amendments(); testErrors(); testSimpleRpc(); - testVoteAndBid(); + testVoteAndBid(all); + testVoteAndBid(all - fixAMMv1_3); testFreeze(); testInvalidAmmField(); } diff --git a/src/xrpld/app/misc/AMMHelpers.h b/src/xrpld/app/misc/AMMHelpers.h index be75abce0..5825d6ad9 100644 --- a/src/xrpld/app/misc/AMMHelpers.h +++ b/src/xrpld/app/misc/AMMHelpers.h @@ -51,6 +51,8 @@ reduceOffer(auto const& amount) } // namespace detail +enum class IsDeposit : bool { No = false, Yes = true }; + /** Calculate LP Tokens given AMM pool reserves. * @param asset1 AMM one side of the pool reserve * @param asset2 AMM another side of the pool reserve @@ -70,7 +72,7 @@ ammLPTokens( * @return tokens */ STAmount -lpTokensIn( +lpTokensOut( STAmount const& asset1Balance, STAmount const& asset1Deposit, STAmount const& lptAMMBalance, @@ -99,7 +101,7 @@ ammAssetIn( * @return tokens out amount */ STAmount -lpTokensOut( +lpTokensIn( STAmount const& asset1Balance, STAmount const& asset1Withdraw, STAmount const& lptAMMBalance, @@ -113,7 +115,7 @@ lpTokensOut( * @return calculated asset amount */ STAmount -withdrawByTokens( +ammAssetOut( STAmount const& assetBalance, STAmount const& lptAMMBalance, STAmount const& lpTokens, @@ -517,13 +519,13 @@ square(Number const& n); * withdraw to cancel out the precision loss. * @param lptAMMBalance LPT AMM Balance * @param lpTokens LP tokens to deposit or withdraw - * @param isDeposit true if deposit, false if withdraw + * @param isDeposit Yes if deposit, No if withdraw */ STAmount adjustLPTokens( STAmount const& lptAMMBalance, STAmount const& lpTokens, - bool isDeposit); + IsDeposit isDeposit); /** Calls adjustLPTokens() and adjusts deposit or withdraw amounts if * the adjusted LP tokens are less than the provided LP tokens. @@ -533,7 +535,7 @@ adjustLPTokens( * @param lptAMMBalance LPT AMM Balance * @param lpTokens LP tokens to deposit or withdraw * @param tfee trading fee in basis points - * @param isDeposit true if deposit, false if withdraw + * @param isDeposit Yes if deposit, No if withdraw * @return */ std::tuple, STAmount> @@ -544,7 +546,7 @@ adjustAmountsByLPTokens( STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee, - bool isDeposit); + IsDeposit isDeposit); /** Positive solution for quadratic equation: * x = (-b + sqrt(b**2 + 4*a*c))/(2*a) @@ -552,6 +554,141 @@ adjustAmountsByLPTokens( Number solveQuadraticEq(Number const& a, Number const& b, Number const& c); +STAmount +multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm); + +namespace detail { + +inline Number::rounding_mode +getLPTokenRounding(IsDeposit isDeposit) +{ + // Minimize on deposit, maximize on withdraw to ensure + // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance + return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward; +} + +inline Number::rounding_mode +getAssetRounding(IsDeposit isDeposit) +{ + // Maximize on deposit, minimize on withdraw to ensure + // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance + return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward; +} + +} // namespace detail + +/** Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas + * calculate the amount as a fractional value of the pool balance. The rounding + * takes place on the last step of multiplying the balance by the fraction if + * AMMv1_3 is enabled. + */ +template +STAmount +getRoundedAsset( + Rules const& rules, + STAmount const& balance, + A const& frac, + IsDeposit isDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + { + if constexpr (std::is_same_v) + return multiply(balance, frac, balance.issue()); + else + return toSTAmount(balance.issue(), balance * frac); + } + auto const rm = detail::getAssetRounding(isDeposit); + return multiply(balance, frac, rm); +} + +/** Round AMM single deposit/withdrawal amount. + * The lambda's are used to delay evaluation until the function + * is executed so that the calculation is not done twice. noRoundCb() is + * called if AMMv1_3 is disabled. Otherwise, the rounding is set and + * the amount is: + * isDeposit is Yes - the balance multiplied by productCb() + * isDeposit is No - the result of productCb(). The rounding is + * the same for all calculations in productCb() + */ +STAmount +getRoundedAsset( + Rules const& rules, + std::function&& noRoundCb, + STAmount const& balance, + std::function&& productCb, + IsDeposit isDeposit); + +/** Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas + * calculate the lptokens as a fractional value of the AMM total lptokens. + * The rounding takes place on the last step of multiplying the balance by + * the fraction if AMMv1_3 is enabled. The tokens are then + * adjusted to factor in the loss in precision (we only keep 16 significant + * digits) when adding the lptokens to the balance. + */ +STAmount +getRoundedLPTokens( + Rules const& rules, + STAmount const& balance, + Number const& frac, + IsDeposit isDeposit); + +/** Round AMM single deposit/withdrawal LPToken amount. + * The lambda's are used to delay evaluation until the function is executed + * so that the calculations are not done twice. + * noRoundCb() is called if AMMv1_3 is disabled. Otherwise, the rounding is set + * and the lptokens are: + * if isDeposit is Yes - the result of productCb(). The rounding is + * the same for all calculations in productCb() + * if isDeposit is No - the balance multiplied by productCb() + * The lptokens are then adjusted to factor in the loss in precision + * (we only keep 16 significant digits) when adding the lptokens to the balance. + */ +STAmount +getRoundedLPTokens( + Rules const& rules, + std::function&& noRoundCb, + STAmount const& lptAMMBalance, + std::function&& productCb, + IsDeposit isDeposit); + +/* Next two functions adjust asset in/out amount to factor in the adjusted + * lptokens. The lptokens are calculated from the asset in/out. The lptokens are + * then adjusted to factor in the loss in precision. The adjusted lptokens might + * be less than the initially calculated tokens. Therefore, the asset in/out + * must be adjusted. The rounding might result in the adjusted amount being + * greater than the original asset in/out amount. If this happens, + * then the original amount is reduced by the difference in the adjusted amount + * and the original amount. The actual tokens and the actual adjusted amount + * are then recalculated. The minimum of the original and the actual + * adjusted amount is returned. + */ +std::pair +adjustAssetInByTokens( + Rules const& rules, + STAmount const& balance, + STAmount const& amount, + STAmount const& lptAMMBalance, + STAmount const& tokens, + std::uint16_t tfee); +std::pair +adjustAssetOutByTokens( + Rules const& rules, + STAmount const& balance, + STAmount const& amount, + STAmount const& lptAMMBalance, + STAmount const& tokens, + std::uint16_t tfee); + +/** Find a fraction of tokens after the tokens are adjusted. The fraction + * is used to adjust equal deposit/withdraw amount. + */ +Number +adjustFracByTokens( + Rules const& rules, + STAmount const& lptAMMBalance, + STAmount const& tokens, + Number const& frac); + } // namespace ripple #endif // RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED diff --git a/src/xrpld/app/misc/detail/AMMHelpers.cpp b/src/xrpld/app/misc/detail/AMMHelpers.cpp index 76b977e9a..97c355b1f 100644 --- a/src/xrpld/app/misc/detail/AMMHelpers.cpp +++ b/src/xrpld/app/misc/detail/AMMHelpers.cpp @@ -27,6 +27,10 @@ ammLPTokens( STAmount const& asset2, Issue const& lptIssue) { + // AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance + auto const rounding = + isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround(); + NumberRoundModeGuard g(rounding); auto const tokens = root2(asset1 * asset2); return toSTAmount(lptIssue, tokens); } @@ -38,7 +42,7 @@ ammLPTokens( * where f1 = 1 - tfee, f2 = (1 - tfee/2)/f1 */ STAmount -lpTokensIn( +lpTokensOut( STAmount const& asset1Balance, STAmount const& asset1Deposit, STAmount const& lptAMMBalance, @@ -48,8 +52,17 @@ lpTokensIn( auto const f2 = feeMultHalf(tfee) / f1; Number const r = asset1Deposit / asset1Balance; auto const c = root2(f2 * f2 + r / f1) - f2; - auto const t = lptAMMBalance * (r - c) / (1 + c); - return toSTAmount(lptAMMBalance.issue(), t); + if (!isFeatureEnabled(fixAMMv1_3)) + { + auto const t = lptAMMBalance * (r - c) / (1 + c); + return toSTAmount(lptAMMBalance.issue(), t); + } + else + { + // minimize tokens out + auto const frac = (r - c) / (1 + c); + return multiply(lptAMMBalance, frac, Number::downward); + } } /* Equation 4 solves equation 3 for b: @@ -78,8 +91,17 @@ ammAssetIn( auto const a = 1 / (t2 * t2); auto const b = 2 * d / t2 - 1 / f1; auto const c = d * d - f2 * f2; - return toSTAmount( - asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c)); + if (!isFeatureEnabled(fixAMMv1_3)) + { + return toSTAmount( + asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c)); + } + else + { + // maximize deposit + auto const frac = solveQuadraticEq(a, b, c); + return multiply(asset1Balance, frac, Number::upward); + } } /* Equation 7: @@ -87,7 +109,7 @@ ammAssetIn( * where R = b/B, c = R*fee + 2 - fee */ STAmount -lpTokensOut( +lpTokensIn( STAmount const& asset1Balance, STAmount const& asset1Withdraw, STAmount const& lptAMMBalance, @@ -96,8 +118,17 @@ lpTokensOut( Number const fr = asset1Withdraw / asset1Balance; auto const f1 = getFee(tfee); auto const c = fr * f1 + 2 - f1; - auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2; - return toSTAmount(lptAMMBalance.issue(), t); + if (!isFeatureEnabled(fixAMMv1_3)) + { + auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2; + return toSTAmount(lptAMMBalance.issue(), t); + } + else + { + // maximize tokens in + auto const frac = (c - root2(c * c - 4 * fr)) / 2; + return multiply(lptAMMBalance, frac, Number::upward); + } } /* Equation 8 solves equation 7 for b: @@ -111,7 +142,7 @@ lpTokensOut( * R = (t1**2 + t1*(f - 2)) / (t1*f - 1) */ STAmount -withdrawByTokens( +ammAssetOut( STAmount const& assetBalance, STAmount const& lptAMMBalance, STAmount const& lpTokens, @@ -119,8 +150,17 @@ withdrawByTokens( { auto const f = getFee(tfee); Number const t1 = lpTokens / lptAMMBalance; - auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); - return toSTAmount(assetBalance.issue(), b); + if (!isFeatureEnabled(fixAMMv1_3)) + { + auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); + return toSTAmount(assetBalance.issue(), b); + } + else + { + // minimize withdraw + auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); + return multiply(assetBalance, frac, Number::downward); + } } Number @@ -133,12 +173,12 @@ STAmount adjustLPTokens( STAmount const& lptAMMBalance, STAmount const& lpTokens, - bool isDeposit) + IsDeposit isDeposit) { // Force rounding downward to ensure adjusted tokens are less or equal // to requested tokens. saveNumberRoundMode rm(Number::setround(Number::rounding_mode::downward)); - if (isDeposit) + if (isDeposit == IsDeposit::Yes) return (lptAMMBalance + lpTokens) - lptAMMBalance; return (lpTokens - lptAMMBalance) + lptAMMBalance; } @@ -151,8 +191,12 @@ adjustAmountsByLPTokens( STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee, - bool isDeposit) + IsDeposit isDeposit) { + // AMMv1_3 amendment adjusts tokens and amounts in deposit/withdraw + if (isFeatureEnabled(fixAMMv1_3)) + return std::make_tuple(amount, amount2, lpTokens); + auto const lpTokensActual = adjustLPTokens(lptAMMBalance, lpTokens, isDeposit); @@ -177,10 +221,10 @@ adjustAmountsByLPTokens( // Single trade auto const amountActual = [&]() { - if (isDeposit) + if (isDeposit == IsDeposit::Yes) return ammAssetIn( amountBalance, lptAMMBalance, lpTokensActual, tfee); - return withdrawByTokens( + return ammAssetOut( amountBalance, lptAMMBalance, lpTokensActual, tfee); }(); @@ -215,4 +259,132 @@ solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c) return (2 * c) / (-b + root2(d)); } +STAmount +multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm) +{ + NumberRoundModeGuard g(rm); + auto const t = amount * frac; + return toSTAmount(amount.issue(), t, rm); +} + +STAmount +getRoundedAsset( + Rules const& rules, + std::function&& noRoundCb, + STAmount const& balance, + std::function&& productCb, + IsDeposit isDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + return toSTAmount(balance.issue(), noRoundCb()); + + auto const rm = detail::getAssetRounding(isDeposit); + if (isDeposit == IsDeposit::Yes) + return multiply(balance, productCb(), rm); + NumberRoundModeGuard g(rm); + return toSTAmount(balance.issue(), productCb(), rm); +} + +STAmount +getRoundedLPTokens( + Rules const& rules, + STAmount const& balance, + Number const& frac, + IsDeposit isDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + return toSTAmount(balance.issue(), balance * frac); + + auto const rm = detail::getLPTokenRounding(isDeposit); + auto const tokens = multiply(balance, frac, rm); + return adjustLPTokens(balance, tokens, isDeposit); +} + +STAmount +getRoundedLPTokens( + Rules const& rules, + std::function&& noRoundCb, + STAmount const& lptAMMBalance, + std::function&& productCb, + IsDeposit isDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + return toSTAmount(lptAMMBalance.issue(), noRoundCb()); + + auto const tokens = [&] { + auto const rm = detail::getLPTokenRounding(isDeposit); + if (isDeposit == IsDeposit::Yes) + { + NumberRoundModeGuard g(rm); + return toSTAmount(lptAMMBalance.issue(), productCb(), rm); + } + return multiply(lptAMMBalance, productCb(), rm); + }(); + return adjustLPTokens(lptAMMBalance, tokens, isDeposit); +} + +std::pair +adjustAssetInByTokens( + Rules const& rules, + STAmount const& balance, + STAmount const& amount, + STAmount const& lptAMMBalance, + STAmount const& tokens, + std::uint16_t tfee) +{ + if (!rules.enabled(fixAMMv1_3)) + return {tokens, amount}; + auto assetAdj = ammAssetIn(balance, lptAMMBalance, tokens, tfee); + auto tokensAdj = tokens; + // Rounding didn't work the right way. + // Try to adjust the original deposit amount by difference + // in adjust and original amount. Then adjust tokens and deposit amount. + if (assetAdj > amount) + { + auto const adjAmount = amount - (assetAdj - amount); + auto const t = lpTokensOut(balance, adjAmount, lptAMMBalance, tfee); + tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::Yes); + assetAdj = ammAssetIn(balance, lptAMMBalance, tokensAdj, tfee); + } + return {tokensAdj, std::min(amount, assetAdj)}; +} + +std::pair +adjustAssetOutByTokens( + Rules const& rules, + STAmount const& balance, + STAmount const& amount, + STAmount const& lptAMMBalance, + STAmount const& tokens, + std::uint16_t tfee) +{ + if (!rules.enabled(fixAMMv1_3)) + return {tokens, amount}; + auto assetAdj = ammAssetOut(balance, lptAMMBalance, tokens, tfee); + auto tokensAdj = tokens; + // Rounding didn't work the right way. + // Try to adjust the original deposit amount by difference + // in adjust and original amount. Then adjust tokens and deposit amount. + if (assetAdj > amount) + { + auto const adjAmount = amount - (assetAdj - amount); + auto const t = lpTokensIn(balance, adjAmount, lptAMMBalance, tfee); + tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::No); + assetAdj = ammAssetOut(balance, lptAMMBalance, tokensAdj, tfee); + } + return {tokensAdj, std::min(amount, assetAdj)}; +} + +Number +adjustFracByTokens( + Rules const& rules, + STAmount const& lptAMMBalance, + STAmount const& tokens, + Number const& frac) +{ + if (!rules.enabled(fixAMMv1_3)) + return frac; + return tokens / lptAMMBalance; +} + } // namespace ripple diff --git a/src/xrpld/app/tx/detail/AMMBid.cpp b/src/xrpld/app/tx/detail/AMMBid.cpp index e8a14c149..9a9730228 100644 --- a/src/xrpld/app/tx/detail/AMMBid.cpp +++ b/src/xrpld/app/tx/detail/AMMBid.cpp @@ -79,6 +79,21 @@ AMMBid::preflight(PreflightContext const& ctx) JLOG(ctx.j.debug()) << "AMM Bid: Invalid number of AuthAccounts."; return temMALFORMED; } + else if (ctx.rules.enabled(fixAMMv1_3)) + { + AccountID account = ctx.tx[sfAccount]; + std::set unique; + for (auto const& obj : authAccounts) + { + auto authAccount = obj[sfAccount]; + if (authAccount == account || unique.contains(authAccount)) + { + JLOG(ctx.j.debug()) << "AMM Bid: Invalid auth.account."; + return temMALFORMED; + } + unique.insert(authAccount); + } + } } return preflight2(ctx); @@ -233,7 +248,9 @@ applyBid( auctionSlot.makeFieldAbsent(sfAuthAccounts); // Burn the remaining bid amount auto const saBurn = adjustLPTokens( - lptAMMBalance, toSTAmount(lptAMMBalance.issue(), burn), false); + lptAMMBalance, + toSTAmount(lptAMMBalance.issue(), burn), + IsDeposit::No); if (saBurn >= lptAMMBalance) { // This error case should never occur. diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 675f56009..50152e401 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -545,7 +545,7 @@ AMMDeposit::deposit( lptAMMBalance, lpTokensDeposit, tfee, - true); + IsDeposit::Yes); if (lpTokensDepositActual <= beast::zero) { @@ -628,6 +628,17 @@ AMMDeposit::deposit( return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual}; } +static STAmount +adjustLPTokensOut( + Rules const& rules, + STAmount const& lptAMMBalance, + STAmount const& lpTokensDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + return lpTokensDeposit; + return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes); +} + /** Proportional deposit of pools assets in exchange for the specified * amount of LPTokens. */ @@ -645,16 +656,25 @@ AMMDeposit::equalDepositTokens( { try { + auto const tokensAdj = + adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; auto const frac = - divide(lpTokensDeposit, lptAMMBalance, lptAMMBalance.issue()); + divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue()); + // amounts factor in the adjusted tokens + auto const amountDeposit = + getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes); + auto const amount2Deposit = + getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes); return deposit( view, ammAccount, amountBalance, - multiply(amountBalance, frac, amountBalance.issue()), - multiply(amount2Balance, frac, amount2Balance.issue()), + amountDeposit, + amount2Deposit, lptAMMBalance, - lpTokensDeposit, + tokensAdj, depositMin, deposit2Min, std::nullopt, @@ -711,37 +731,55 @@ AMMDeposit::equalDepositLimit( std::uint16_t tfee) { auto frac = Number{amount} / amountBalance; - auto tokens = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac); - if (tokens == beast::zero) - return {tecAMM_FAILED, STAmount{}}; - auto const amount2Deposit = amount2Balance * frac; + auto tokensAdj = + getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes); + if (tokensAdj == beast::zero) + { + if (!view.rules().enabled(fixAMMv1_3)) + return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE + else + return {tecAMM_INVALID_TOKENS, STAmount{}}; + } + // factor in the adjusted tokens + frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); + auto const amount2Deposit = + getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes); if (amount2Deposit <= amount2) return deposit( view, ammAccount, amountBalance, amount, - toSTAmount(amount2Balance.issue(), amount2Deposit), + amount2Deposit, lptAMMBalance, - tokens, + tokensAdj, std::nullopt, std::nullopt, lpTokensDepositMin, tfee); frac = Number{amount2} / amount2Balance; - tokens = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac); - if (tokens == beast::zero) - return {tecAMM_FAILED, STAmount{}}; - auto const amountDeposit = amountBalance * frac; + tokensAdj = + getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes); + if (tokensAdj == beast::zero) + { + if (!view.rules().enabled(fixAMMv1_3)) + return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE + else + return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE + } + // factor in the adjusted tokens + frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); + auto const amountDeposit = + getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes); if (amountDeposit <= amount) return deposit( view, ammAccount, amountBalance, - toSTAmount(amountBalance.issue(), amountDeposit), + amountDeposit, amount2, lptAMMBalance, - tokens, + tokensAdj, std::nullopt, std::nullopt, lpTokensDepositMin, @@ -767,17 +805,30 @@ AMMDeposit::singleDeposit( std::optional const& lpTokensDepositMin, std::uint16_t tfee) { - auto const tokens = lpTokensIn(amountBalance, amount, lptAMMBalance, tfee); + auto const tokens = adjustLPTokensOut( + view.rules(), + lptAMMBalance, + lpTokensOut(amountBalance, amount, lptAMMBalance, tfee)); if (tokens == beast::zero) - return {tecAMM_FAILED, STAmount{}}; + { + if (!view.rules().enabled(fixAMMv1_3)) + return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE + else + return {tecAMM_INVALID_TOKENS, STAmount{}}; + } + // factor in the adjusted tokens + auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens( + view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE return deposit( view, ammAccount, amountBalance, - amount, + amountDepositAdj, std::nullopt, lptAMMBalance, - tokens, + tokensAdj, std::nullopt, std::nullopt, lpTokensDepositMin, @@ -801,8 +852,13 @@ AMMDeposit::singleDepositTokens( STAmount const& lpTokensDeposit, std::uint16_t tfee) { + auto const tokensAdj = + adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; + // the adjusted tokens are factored in auto const amountDeposit = - ammAssetIn(amountBalance, lptAMMBalance, lpTokensDeposit, tfee); + ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee); if (amountDeposit > amount) return {tecAMM_FAILED, STAmount{}}; return deposit( @@ -812,7 +868,7 @@ AMMDeposit::singleDepositTokens( amountDeposit, std::nullopt, lptAMMBalance, - lpTokensDeposit, + tokensAdj, std::nullopt, std::nullopt, std::nullopt, @@ -856,20 +912,32 @@ AMMDeposit::singleDepositEPrice( { if (amount != beast::zero) { - auto const tokens = - lpTokensIn(amountBalance, amount, lptAMMBalance, tfee); + auto const tokens = adjustLPTokensOut( + view.rules(), + lptAMMBalance, + lpTokensOut(amountBalance, amount, lptAMMBalance, tfee)); if (tokens <= beast::zero) - return {tecAMM_FAILED, STAmount{}}; - auto const ep = Number{amount} / tokens; + { + if (!view.rules().enabled(fixAMMv1_3)) + return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE + else + return {tecAMM_INVALID_TOKENS, STAmount{}}; + } + // factor in the adjusted tokens + auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens( + view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE + auto const ep = Number{amountDepositAdj} / tokensAdj; if (ep <= ePrice) return deposit( view, ammAccount, amountBalance, - amount, + amountDepositAdj, std::nullopt, lptAMMBalance, - tokens, + tokensAdj, std::nullopt, std::nullopt, std::nullopt, @@ -900,21 +968,37 @@ AMMDeposit::singleDepositEPrice( auto const a1 = c * c; auto const b1 = c * c * f2 * f2 + 2 * c - d * d; auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2; - auto const amountDeposit = toSTAmount( - amountBalance.issue(), - f1 * amountBalance * solveQuadraticEq(a1, b1, c1)); + auto amtNoRoundCb = [&] { + return f1 * amountBalance * solveQuadraticEq(a1, b1, c1); + }; + auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); }; + auto const amountDeposit = getRoundedAsset( + view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes); if (amountDeposit <= beast::zero) return {tecAMM_FAILED, STAmount{}}; - auto const tokens = - toSTAmount(lptAMMBalance.issue(), amountDeposit / ePrice); + auto tokNoRoundCb = [&] { return amountDeposit / ePrice; }; + auto tokProdCb = [&] { return amountDeposit / ePrice; }; + auto const tokens = getRoundedLPTokens( + view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes); + // factor in the adjusted tokens + auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens( + view.rules(), + amountBalance, + amountDeposit, + lptAMMBalance, + tokens, + tfee); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE + return deposit( view, ammAccount, amountBalance, - amountDeposit, + amountDepositAdj, std::nullopt, lptAMMBalance, - tokens, + tokensAdj, std::nullopt, std::nullopt, std::nullopt, diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 4fccec937..233ebb457 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -523,7 +523,7 @@ AMMWithdraw::withdraw( lpTokensAMMBalance, lpTokensWithdraw, tfee, - false); + IsDeposit::No); return std::make_tuple( amountWithdraw, amount2Withdraw, lpTokensWithdraw); }(); @@ -682,6 +682,20 @@ AMMWithdraw::withdraw( amount2WithdrawActual); } +static STAmount +adjustLPTokensIn( + Rules const& rules, + STAmount const& lptAMMBalance, + STAmount const& lpTokensWithdraw, + WithdrawAll withdrawAll) +{ + if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes) + return lpTokensWithdraw; + return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No); +} + +/** Proportional withdrawal of pool assets for the amount of LPTokens. + */ std::pair AMMWithdraw::equalWithdrawTokens( Sandbox& view, @@ -785,16 +799,22 @@ AMMWithdraw::equalWithdrawTokens( journal); } - auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue()); - auto const withdrawAmount = - multiply(amountBalance, frac, amountBalance.issue()); - auto const withdraw2Amount = - multiply(amount2Balance, frac, amount2Balance.issue()); + auto const tokensAdj = adjustLPTokensIn( + view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return { + tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt}; + // the adjusted tokens are factored in + auto const frac = divide(tokensAdj, lptAMMBalance, noIssue()); + auto const amountWithdraw = + getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No); + auto const amount2Withdraw = + getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No); // LP is making equal withdrawal by tokens but the requested amount // of LP tokens is likely too small and results in one-sided pool // withdrawal due to round off. Fail so the user withdraws // more tokens. - if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero) + if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero) return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}}; return withdraw( @@ -803,10 +823,10 @@ AMMWithdraw::equalWithdrawTokens( ammAccount, account, amountBalance, - withdrawAmount, - withdraw2Amount, + amountWithdraw, + amount2Withdraw, lptAMMBalance, - lpTokensWithdraw, + tokensAdj, tfee, freezeHanding, withdrawAll, @@ -861,7 +881,16 @@ AMMWithdraw::equalWithdrawLimit( std::uint16_t tfee) { auto frac = Number{amount} / amountBalance; - auto const amount2Withdraw = amount2Balance * frac; + auto amount2Withdraw = + getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No); + auto tokensAdj = + getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; + // factor in the adjusted tokens + frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); + amount2Withdraw = + getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No); if (amount2Withdraw <= amount2) { return withdraw( @@ -870,26 +899,42 @@ AMMWithdraw::equalWithdrawLimit( ammAccount, amountBalance, amount, - toSTAmount(amount2.issue(), amount2Withdraw), + amount2Withdraw, lptAMMBalance, - toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac), + tokensAdj, tfee); } frac = Number{amount2} / amount2Balance; - auto const amountWithdraw = amountBalance * frac; - XRPL_ASSERT( - amountWithdraw <= amount, - "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw"); + auto amountWithdraw = + getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No); + tokensAdj = + getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE + // factor in the adjusted tokens + frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); + amountWithdraw = + getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No); + if (!view.rules().enabled(fixAMMv1_3)) + { + // LCOV_EXCL_START + XRPL_ASSERT( + amountWithdraw <= amount, + "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw"); + // LCOV_EXCL_STOP + } + else if (amountWithdraw > amount) + return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE return withdraw( view, ammSle, ammAccount, amountBalance, - toSTAmount(amount.issue(), amountWithdraw), + amountWithdraw, amount2, lptAMMBalance, - toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac), + tokensAdj, tfee); } @@ -908,19 +953,32 @@ AMMWithdraw::singleWithdraw( STAmount const& amount, std::uint16_t tfee) { - auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee); + auto const tokens = adjustLPTokensIn( + view.rules(), + lptAMMBalance, + lpTokensIn(amountBalance, amount, lptAMMBalance, tfee), + isWithdrawAll(ctx_.tx)); if (tokens == beast::zero) - return {tecAMM_FAILED, STAmount{}}; - + { + if (!view.rules().enabled(fixAMMv1_3)) + return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE + else + return {tecAMM_INVALID_TOKENS, STAmount{}}; + } + // factor in the adjusted tokens + auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens( + view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE return withdraw( view, ammSle, ammAccount, amountBalance, - amount, + amountWithdrawAdj, std::nullopt, lptAMMBalance, - tokens, + tokensAdj, tfee); } @@ -945,8 +1003,13 @@ AMMWithdraw::singleWithdrawTokens( STAmount const& lpTokensWithdraw, std::uint16_t tfee) { + auto const tokensAdj = adjustLPTokensIn( + view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx)); + if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}}; + // the adjusted tokens are factored in auto const amountWithdraw = - withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee); + ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee); if (amount == beast::zero || amountWithdraw >= amount) { return withdraw( @@ -957,7 +1020,7 @@ AMMWithdraw::singleWithdrawTokens( amountWithdraw, std::nullopt, lptAMMBalance, - lpTokensWithdraw, + tokensAdj, tfee); } @@ -1006,11 +1069,27 @@ AMMWithdraw::singleWithdrawEPrice( // t = T*(T + A*E*(f - 2))/(T*f - A*E) Number const ae = amountBalance * ePrice; auto const f = getFee(tfee); - auto const tokens = lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / - (lptAMMBalance * f - ae); - if (tokens <= 0) - return {tecAMM_FAILED, STAmount{}}; - auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice); + auto tokNoRoundCb = [&] { + return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / + (lptAMMBalance * f - ae); + }; + auto tokProdCb = [&] { + return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); + }; + auto const tokensAdj = getRoundedLPTokens( + view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No); + if (tokensAdj <= beast::zero) + { + if (!view.rules().enabled(fixAMMv1_3)) + return {tecAMM_FAILED, STAmount{}}; + else + return {tecAMM_INVALID_TOKENS, STAmount{}}; + } + auto amtNoRoundCb = [&] { return tokensAdj / ePrice; }; + auto amtProdCb = [&] { return tokensAdj / ePrice; }; + // the adjusted tokens are factored in + auto const amountWithdraw = getRoundedAsset( + view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No); if (amount == beast::zero || amountWithdraw >= amount) { return withdraw( @@ -1021,7 +1100,7 @@ AMMWithdraw::singleWithdrawEPrice( amountWithdraw, std::nullopt, lptAMMBalance, - toSTAmount(lptAMMBalance.issue(), tokens), + tokensAdj, tfee); } diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.h b/src/xrpld/app/tx/detail/AMMWithdraw.h index ae9328cb0..1de91fd78 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.h +++ b/src/xrpld/app/tx/detail/AMMWithdraw.h @@ -301,7 +301,7 @@ private: std::uint16_t tfee); /** Check from the flags if it's withdraw all */ - WithdrawAll + static WithdrawAll isWithdrawAll(STTx const& tx); }; diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index aca956dad..9484eefd7 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -17,6 +17,9 @@ */ //============================================================================== +#include +#include +#include #include #include @@ -1674,4 +1677,309 @@ ValidPermissionedDomain::finalize( (sleStatus_[1] ? check(*sleStatus_[1], j) : true); } +void +ValidAMM::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete) + return; + + if (after) + { + auto const type = after->getType(); + // AMM object changed + if (type == ltAMM) + { + ammAccount_ = after->getAccountID(sfAccount); + lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance); + } + // AMM pool changed + else if ( + (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) || + (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID))) + { + ammPoolChanged_ = true; + } + } + + if (before) + { + // AMM object changed + if (before->getType() == ltAMM) + { + lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance); + } + } +} + +static bool +validBalances( + STAmount const& amount, + STAmount const& amount2, + STAmount const& lptAMMBalance, + ValidAMM::ZeroAllowed zeroAllowed) +{ + bool const positive = amount > beast::zero && amount2 > beast::zero && + lptAMMBalance > beast::zero; + if (zeroAllowed == ValidAMM::ZeroAllowed::Yes) + return positive || + (amount == beast::zero && amount2 == beast::zero && + lptAMMBalance == beast::zero); + return positive; +} + +bool +ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const +{ + if (lptAMMBalanceAfter_ != lptAMMBalanceBefore_ || ammPoolChanged_) + { + // LPTokens and the pool can not change on vote + // LCOV_EXCL_START + JLOG(j.error()) << "AMMVote invariant failed: " + << lptAMMBalanceBefore_.value_or(STAmount{}) << " " + << lptAMMBalanceAfter_.value_or(STAmount{}) << " " + << ammPoolChanged_; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const +{ + if (ammPoolChanged_) + { + // The pool can not change on bid + // LCOV_EXCL_START + JLOG(j.error()) << "AMMBid invariant failed: pool changed"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + // LPTokens are burnt, therefore there should be fewer LPTokens + else if ( + lptAMMBalanceBefore_ && lptAMMBalanceAfter_ && + (*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ || + *lptAMMBalanceAfter_ <= beast::zero)) + { + // LCOV_EXCL_START + JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ + << " " << *lptAMMBalanceAfter_; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::finalizeCreate( + STTx const& tx, + ReadView const& view, + bool enforce, + beast::Journal const& j) const +{ + if (!ammAccount_) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "AMMCreate invariant failed: AMM object is not created"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + else + { + auto const [amount, amount2] = ammPoolHolds( + view, + *ammAccount_, + tx[sfAmount].get(), + tx[sfAmount2].get(), + fhIGNORE_FREEZE, + j); + // Create invariant: + // sqrt(amount * amount2) == LPTokens + // all balances are greater than zero + if (!validBalances( + amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) || + ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) != + *lptAMMBalanceAfter_) + { + JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " + << amount2 << " " << *lptAMMBalanceAfter_; + if (enforce) + return false; + } + } + + return true; +} + +bool +ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const +{ + if (ammAccount_) + { + // LCOV_EXCL_START + std::string const msg = (res == tesSUCCESS) + ? "AMM object is not deleted on tesSUCCESS" + : "AMM object is changed on tecINCOMPLETE"; + JLOG(j.error()) << "AMMDelete invariant failed: " << msg; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const +{ + if (ammAccount_) + { + // LCOV_EXCL_START + JLOG(j.error()) << "AMM swap invariant failed: AMM object changed"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::generalInvariant( + ripple::STTx const& tx, + ripple::ReadView const& view, + ZeroAllowed zeroAllowed, + beast::Journal const& j) const +{ + auto const [amount, amount2] = ammPoolHolds( + view, + *ammAccount_, + tx[sfAsset].get(), + tx[sfAsset2].get(), + fhIGNORE_FREEZE, + j); + // Deposit and Withdrawal invariant: + // sqrt(amount * amount2) >= LPTokens + // all balances are greater than zero + // unless on last withdrawal + auto const poolProductMean = root2(amount * amount2); + bool const nonNegativeBalances = + validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed); + bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_; + // Allow for a small relative error if strongInvariantCheck fails + auto weakInvariantCheck = [&]() { + return *lptAMMBalanceAfter_ != beast::zero && + withinRelativeDistance( + poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11}); + }; + if (!nonNegativeBalances || + (!strongInvariantCheck && !weakInvariantCheck())) + { + JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: " + << tx.getHash(HashPrefix::transactionID) << " " + << ammPoolChanged_ << " " << amount << " " << amount2 + << " " << poolProductMean << " " + << lptAMMBalanceAfter_->getText() << " " + << ((*lptAMMBalanceAfter_ == beast::zero) + ? Number{1} + : ((*lptAMMBalanceAfter_ - poolProductMean) / + poolProductMean)); + return false; + } + + return true; +} + +bool +ValidAMM::finalizeDeposit( + ripple::STTx const& tx, + ripple::ReadView const& view, + bool enforce, + beast::Journal const& j) const +{ + if (!ammAccount_) + { + // LCOV_EXCL_START + JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce) + return false; + + return true; +} + +bool +ValidAMM::finalizeWithdraw( + ripple::STTx const& tx, + ripple::ReadView const& view, + bool enforce, + beast::Journal const& j) const +{ + if (!ammAccount_) + { + // Last Withdraw or Clawback deleted AMM + } + else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j)) + { + if (enforce) + return false; + } + + return true; +} + +bool +ValidAMM::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + // Delete may return tecINCOMPLETE if there are too many + // trustlines to delete. + if (result != tesSUCCESS && result != tecINCOMPLETE) + return true; + + bool const enforce = view.rules().enabled(fixAMMv1_3); + + switch (tx.getTxnType()) + { + case ttAMM_CREATE: + return finalizeCreate(tx, view, enforce, j); + case ttAMM_DEPOSIT: + return finalizeDeposit(tx, view, enforce, j); + case ttAMM_CLAWBACK: + case ttAMM_WITHDRAW: + return finalizeWithdraw(tx, view, enforce, j); + case ttAMM_BID: + return finalizeBid(enforce, j); + case ttAMM_VOTE: + return finalizeVote(enforce, j); + case ttAMM_DELETE: + return finalizeDelete(enforce, result, j); + case ttCHECK_CASH: + case ttOFFER_CREATE: + case ttPAYMENT: + return finalizeDEX(enforce, j); + default: + break; + } + + return true; +} + } // namespace ripple diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index 364ece036..e854fd649 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -617,6 +617,69 @@ public: beast::Journal const&); }; +class ValidAMM +{ + std::optional ammAccount_; + std::optional lptAMMBalanceAfter_; + std::optional lptAMMBalanceBefore_; + bool ammPoolChanged_; + +public: + enum class ZeroAllowed : bool { No = false, Yes = true }; + + ValidAMM() : ammPoolChanged_{false} + { + } + void + visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&); + + bool + finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const&); + +private: + bool + finalizeBid(bool enforce, beast::Journal const&) const; + bool + finalizeVote(bool enforce, beast::Journal const&) const; + bool + finalizeCreate( + STTx const&, + ReadView const&, + bool enforce, + beast::Journal const&) const; + bool + finalizeDelete(bool enforce, TER res, beast::Journal const&) const; + bool + finalizeDeposit( + STTx const&, + ReadView const&, + bool enforce, + beast::Journal const&) const; + // Includes clawback + bool + finalizeWithdraw( + STTx const&, + ReadView const&, + bool enforce, + beast::Journal const&) const; + bool + finalizeDEX(bool enforce, beast::Journal const&) const; + bool + generalInvariant( + STTx const&, + ReadView const&, + ZeroAllowed zeroAllowed, + beast::Journal const&) const; +}; + // additional invariant checks can be declared above and then added to this // tuple using InvariantChecks = std::tuple< @@ -636,7 +699,8 @@ using InvariantChecks = std::tuple< NFTokenCountTracking, ValidClawback, ValidMPTIssuance, - ValidPermissionedDomain>; + ValidPermissionedDomain, + ValidAMM>; /** * @brief get a tuple of all invariant checks diff --git a/src/xrpld/app/tx/detail/Offer.h b/src/xrpld/app/tx/detail/Offer.h index 23129952c..a26a35e60 100644 --- a/src/xrpld/app/tx/detail/Offer.h +++ b/src/xrpld/app/tx/detail/Offer.h @@ -21,6 +21,8 @@ #define RIPPLE_APP_BOOK_OFFER_H_INCLUDED #include + +#include #include #include #include @@ -169,8 +171,24 @@ public: * always returns true. */ bool - checkInvariant(TAmounts const&, beast::Journal j) const + checkInvariant(TAmounts const& consumed, beast::Journal j) const { + if (!isFeatureEnabled(fixAMMv1_3)) + return true; + + if (consumed.in > m_amounts.in || consumed.out > m_amounts.out) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "AMMOffer::checkInvariant failed: consumed " + << to_string(consumed.in) << " " << to_string(consumed.out) + << " amounts " << to_string(m_amounts.in) << " " + << to_string(m_amounts.out); + + return false; + // LCOV_EXCL_STOP + } + return true; } }; From 503dee619aeddfbd3e16a174d0cac6c0b7ea56b3 Mon Sep 17 00:00:00 2001 From: tequ Date: Thu, 19 Feb 2026 12:14:41 +0900 Subject: [PATCH 02/18] Merge fixAMMv1_3 amendment into featureAMM amendment --- include/xrpl/protocol/Feature.h | 2 +- include/xrpl/protocol/detail/features.macro | 1 - src/test/app/AMMClawback_test.cpp | 544 +++++--------------- src/test/app/AMMExtended_test.cpp | 7 - src/test/app/AMM_test.cpp | 482 +++++------------ src/test/jtx/impl/AMM.cpp | 6 - src/test/rpc/AMMInfo_test.cpp | 29 +- src/xrpld/app/misc/AMMHelpers.h | 7 - src/xrpld/app/misc/detail/AMMHelpers.cpp | 121 +---- src/xrpld/app/tx/detail/AMMBid.cpp | 2 +- src/xrpld/app/tx/detail/AMMDeposit.cpp | 32 +- src/xrpld/app/tx/detail/AMMWithdraw.cpp | 32 +- src/xrpld/app/tx/detail/InvariantCheck.cpp | 2 +- src/xrpld/app/tx/detail/Offer.h | 3 - 14 files changed, 323 insertions(+), 947 deletions(-) diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 3f7a39778..ed0732930 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -80,7 +80,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 = 114; +static constexpr std::size_t numFeatures = 113; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index c04e40094..3f48ac714 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -31,7 +31,6 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. -XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 9ccd59b58..4d2a99593 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -581,12 +581,8 @@ class AMMClawback_test : public jtx::AMMTest AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(4000), EUR(5000), IOUAmount{4472135954999579, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999579, -12})); // gw clawback 1000 USD from the AMM pool env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)), @@ -605,20 +601,12 @@ class AMMClawback_test : public jtx::AMMTest // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the // current balance is 3000 USD and 3750 EUR. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(3000), EUR(3750), IOUAmount{3354101966249685, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(3000), EUR(3750), IOUAmount{3354101966249684, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(3000), EUR(3750), IOUAmount{3354101966249684, -12})); // Alice has 3/4 of its initial lptokens Left. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{3354101966249685, -12})); - else - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{3354101966249684, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{3354101966249684, -12})); // gw clawback another 500 USD from the AMM pool. env(amm::ammClawback(gw, alice, USD, EUR, USD(500)), @@ -629,21 +617,10 @@ class AMMClawback_test : public jtx::AMMTest // AMM pool. env.require(balance(alice, gw["USD"](2000))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2500000000000001), -12}, - STAmount{EUR, UINT64_C(3125000000000001), -12}, - IOUAmount{2795084971874738, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(2500), EUR(3125), IOUAmount{2795084971874737, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(2500), EUR(3125), IOUAmount{2795084971874737, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - env.balance(alice, EUR) == - STAmount(EUR, UINT64_C(2874999999999999), -12)); - else - BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875)); // gw clawback small amount, 1 USD. env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS)); @@ -652,21 +629,10 @@ class AMMClawback_test : public jtx::AMMTest // Another 1 USD / 1.25 EUR was withdrawn. env.require(balance(alice, gw["USD"](2000))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2499000000000002), -12}, - STAmount{EUR, UINT64_C(3123750000000002), -12}, - IOUAmount{2793966937885989, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - env.balance(alice, EUR) == - STAmount(EUR, UINT64_C(2'876'249999999998), -12)); - else - BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25)); // gw clawback 4000 USD, exceeding the current balance. We // will clawback all. @@ -739,26 +705,14 @@ class AMMClawback_test : public jtx::AMMTest // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR. AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); amm2.deposit(alice, EUR(1000), XRP(3000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); amm2.deposit(bob, EUR(1000), XRP(3000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm2.expectBalances( - EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9})); env.close(); auto aliceXrpBalance = env.balance(alice, XRP); @@ -781,18 +735,10 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT( expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(2500), XRP(5000), IOUAmount{3535533905932738, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(2500), XRP(5000), IOUAmount{3535533905932737, -9})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{7071067811865480, -10})); - else - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{7071067811865474, -10})); + BEAST_EXPECT(amm.expectBalances( + USD(2500), XRP(5000), IOUAmount{3535533905932737, -9})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865474, -10})); BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9})); @@ -806,26 +752,12 @@ class AMMClawback_test : public jtx::AMMTest // Bob gets 20 XRP back. BEAST_EXPECT( expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2490000000000001), -12}, - XRP(4980), - IOUAmount{3521391770309008, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{7071067811865480, -10})); - else - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{7071067811865474, -10})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); - else - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); + BEAST_EXPECT(amm.expectBalances( + USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{7071067811865474, -10})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); // gw2 clawback 200 EUR from amm2. env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)), @@ -838,24 +770,12 @@ class AMMClawback_test : public jtx::AMMTest // Alice gets 600 XRP back. BEAST_EXPECT(expectLedgerEntryRoot( env, alice, aliceXrpBalance + XRP(1000) + XRP(600))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm2.expectBalances( - EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm2.expectLPTokens( - alice, IOUAmount{1385640646055103, -9})); - else - BEAST_EXPECT(amm2.expectLPTokens( - alice, IOUAmount{1385640646055102, -9})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9})); - else - BEAST_EXPECT( - amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9})); + BEAST_EXPECT( + amm2.expectLPTokens(alice, IOUAmount{1385640646055102, -9})); + BEAST_EXPECT( + amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9})); // gw claw back 1000 USD from alice in amm, which exceeds alice's // balance. This will clawback all the remaining LP tokens of alice @@ -868,34 +788,18 @@ class AMMClawback_test : public jtx::AMMTest env.require(balance(bob, gw["USD"](4000))); // Alice gets 1000 XRP back. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - - XRPAmount{1})); + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - + XRPAmount{1})); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); - else - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(1990000000000001), -12}, - XRP(3980), - IOUAmount{2814284989122460, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(1'990), - XRPAmount{3'980'000'001}, - IOUAmount{2814284989122459, -9})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); + BEAST_EXPECT(amm.expectBalances( + USD(1'990), + XRPAmount{3'980'000'001}, + IOUAmount{2814284989122459, -9})); // gw clawback 1000 USD from bob in amm, which also exceeds bob's // balance in amm. All bob's lptoken in amm will be consumed, which @@ -907,17 +811,11 @@ class AMMClawback_test : public jtx::AMMTest env.require(balance(alice, gw["USD"](5000))); env.require(balance(bob, gw["USD"](4000))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - - XRPAmount{1})); + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - + XRPAmount{1})); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, bobXrpBalance + XRP(20) + XRP(1980))); @@ -937,30 +835,19 @@ class AMMClawback_test : public jtx::AMMTest // Alice gets another 2400 XRP back, bob's XRP balance remains the // same. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400) - XRPAmount{1})); + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + XRP(2400) - + XRPAmount{1})); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, bobXrpBalance + XRP(20) + XRP(1980))); // Alice now does not have any lptoken in amm2 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's // balance. All bob's lptokens will be consumed, which corresponds @@ -974,18 +861,11 @@ class AMMClawback_test : public jtx::AMMTest // Bob gets another 3000 XRP back. Alice's XRP balance remains the // same. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400) - XRPAmount{1})); + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + XRP(2400) - + XRPAmount{1})); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000))); @@ -993,12 +873,8 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); } } @@ -1056,45 +932,21 @@ class AMMClawback_test : public jtx::AMMTest AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(4000), EUR(5000), IOUAmount{4472135954999580, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(4000), EUR(5000), IOUAmount{4472135954999579, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(4000), EUR(5000), IOUAmount{4472135954999579, -12})); amm.deposit(bob, USD(2000), EUR(2500)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(6000), EUR(7500), IOUAmount{6708203932499370, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(6000), EUR(7500), IOUAmount{6708203932499368, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(6000), EUR(7500), IOUAmount{6708203932499368, -12})); amm.deposit(carol, USD(1000), EUR(1250)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(7000), EUR(8750), IOUAmount{7826237921249265, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(7000), EUR(8750), IOUAmount{7826237921249262, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(7000), EUR(8750), IOUAmount{7826237921249262, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{4472135954999580, -12})); - else - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{4472135954999579, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12})); - else - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - carol, IOUAmount{1118033988749895, -12})); - else - BEAST_EXPECT(amm.expectLPTokens( - carol, IOUAmount{1118033988749894, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4472135954999579, -12})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12})); + BEAST_EXPECT( + amm.expectLPTokens(carol, IOUAmount{1118033988749894, -12})); env.require(balance(alice, gw["USD"](2000))); env.require(balance(alice, gw2["EUR"](1000))); @@ -1108,30 +960,16 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(4999999999999999), -12}, - STAmount{EUR, UINT64_C(6249999999999999), -12}, - IOUAmount{5590169943749475, -12})); - else - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(5000000000000001), -12}, - STAmount{EUR, UINT64_C(6250000000000001), -12}, - IOUAmount{5590169943749473, -12})); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(5000000000000001), -12}, + STAmount{EUR, UINT64_C(6250000000000001), -12}, + IOUAmount{5590169943749473, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{4472135954999580, -12})); - else - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{4472135954999579, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4472135954999579, -12})); BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - carol, IOUAmount{1118033988749895, -12})); - else - BEAST_EXPECT(amm.expectLPTokens( - carol, IOUAmount{1118033988749894, -12})); + BEAST_EXPECT( + amm.expectLPTokens(carol, IOUAmount{1118033988749894, -12})); // Bob will get 2500 EUR back. env.require(balance(alice, gw["USD"](2000))); @@ -1140,14 +978,9 @@ class AMMClawback_test : public jtx::AMMTest env.balance(bob, USD) == STAmount(USD, UINT64_C(3000000000000000), -12)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - env.balance(bob, EUR) == - STAmount(EUR, UINT64_C(5000000000000001), -12)); - else - BEAST_EXPECT( - env.balance(bob, EUR) == - STAmount(EUR, UINT64_C(4999999999999999), -12)); + BEAST_EXPECT( + env.balance(bob, EUR) == + STAmount(EUR, UINT64_C(4999999999999999), -12)); env.require(balance(carol, gw["USD"](3000))); env.require(balance(carol, gw2["EUR"](2750))); @@ -1155,23 +988,13 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(3999999999999999), -12}, - STAmount{EUR, UINT64_C(4999999999999999), -12}, - IOUAmount{4472135954999580, -12})); - else - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(4000000000000001), -12}, - STAmount{EUR, UINT64_C(5000000000000002), -12}, - IOUAmount{4472135954999579, -12})); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(4000000000000001), -12}, + STAmount{EUR, UINT64_C(5000000000000002), -12}, + IOUAmount{4472135954999579, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{4472135954999580, -12})); - else - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{4472135954999579, -12})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{4472135954999579, -12})); BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0))); @@ -1210,26 +1033,14 @@ class AMMClawback_test : public jtx::AMMTest // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD. AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(10000), XRP(2000), IOUAmount{4472135954999579, -9})); + BEAST_EXPECT(amm.expectBalances( + USD(10000), XRP(2000), IOUAmount{4472135954999579, -9})); amm.deposit(alice, USD(1000), XRP(200)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(11000), XRP(2200), IOUAmount{4919349550499538, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(11000), XRP(2200), IOUAmount{4919349550499536, -9})); + BEAST_EXPECT(amm.expectBalances( + USD(11000), XRP(2200), IOUAmount{4919349550499536, -9})); amm.deposit(bob, USD(2000), XRP(400)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(13000), XRP(2600), IOUAmount{5813776741499453, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(13000), XRP(2600), IOUAmount{5813776741499451, -9})); + BEAST_EXPECT(amm.expectBalances( + USD(13000), XRP(2600), IOUAmount{5813776741499451, -9})); env.close(); auto aliceXrpBalance = env.balance(alice, XRP); @@ -1239,34 +1050,22 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(12000), XRP(2400), IOUAmount{5366563145999495, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(12000), - XRPAmount(2400000001), - IOUAmount{5366563145999494, -9})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(200))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1})); + BEAST_EXPECT(amm.expectBalances( + USD(12000), + XRPAmount(2400000001), + IOUAmount{5366563145999494, -9})); + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1})); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); // gw clawback all bob's USD in amm. (2000 USD / 400 XRP) env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(10000), XRP(2000), IOUAmount{4472135954999580, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(10000), - XRPAmount(2000000001), - IOUAmount{4472135954999579, -9})); + BEAST_EXPECT(amm.expectBalances( + USD(10000), + XRPAmount(2000000001), + IOUAmount{4472135954999579, -9})); BEAST_EXPECT( expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400))); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); @@ -1322,10 +1121,7 @@ class AMMClawback_test : public jtx::AMMTest amm.deposit(bob, USD(4000), EUR(1000)); BEAST_EXPECT( amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); - if (!features[fixAMMv1_3]) - amm.deposit(carol, USD(2000), EUR(500)); - else - amm.deposit(carol, USD(2000.25), EUR(500)); + amm.deposit(carol, USD(2000.25), EUR(500)); BEAST_EXPECT( amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000))); // gw clawback 1000 USD from carol. @@ -1341,12 +1137,9 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); - else - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(5999'999999999999), -12)); + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); // 250 EUR goes back to carol. BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); @@ -1368,12 +1161,9 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); // 250 EUR did not go back to bob because tfClawTwoAssets is set. BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); - else - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(5999'999999999999), -12)); + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); // gw clawback all USD from alice and set tfClawTwoAssets. @@ -1390,12 +1180,9 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); - else - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(5999'999999999999), -12)); + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); } @@ -1580,21 +1367,10 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 1000 EUR from gw. env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(4500), - STAmount(EUR, UINT64_C(9000000000000001), -12), - IOUAmount{6363961030678928, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(4500), EUR(9000), IOUAmount{6363961030678928, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(4500), EUR(9000), IOUAmount{6363961030678928, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); - else - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1607,21 +1383,10 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 4000 EUR from alice. env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - USD(2500), - STAmount(EUR, UINT64_C(5000000000000001), -12), - IOUAmount{3535533905932738, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(2500), EUR(5000), IOUAmount{3535533905932738, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(2500), EUR(5000), IOUAmount{3535533905932738, -12})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); - else - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12})); @@ -1885,10 +1650,7 @@ class AMMClawback_test : public jtx::AMMTest amm.deposit(bob, USD(4000), EUR(1000)); BEAST_EXPECT( amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000))); - if (!features[fixAMMv1_3]) - amm.deposit(carol, USD(2000), EUR(500)); - else - amm.deposit(carol, USD(2000.25), EUR(500)); + amm.deposit(carol, USD(2000.25), EUR(500)); BEAST_EXPECT( amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000))); @@ -1910,12 +1672,9 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); - else - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(5999'999999999999), -12)); + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); // 250 EUR goes back to carol. BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); @@ -1937,12 +1696,9 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); // 250 EUR did not go back to bob because tfClawTwoAssets is set. BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); - else - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(5999'999999999999), -12)); + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); // gw clawback all USD from alice and set tfClawTwoAssets. @@ -1960,12 +1716,9 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000)); BEAST_EXPECT(env.balance(bob, USD) == USD(5000)); BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(env.balance(carol, USD) == USD(6000)); - else - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(5999'999999999999), -12)); + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(5999'999999999999), -12)); BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750)); } } @@ -2013,23 +1766,13 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(5656854249492380), -13), - XRP(70.710678), - IOUAmount(200000))); - else - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(565'685424949238), -12), - XRP(70.710679), - IOUAmount(200000))); + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(565'685424949238), -12), + XRP(70.710679), + IOUAmount(200000))); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(29.289322))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(29.289321))); + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(29.289321))); } void @@ -2040,18 +1783,13 @@ class AMMClawback_test : public jtx::AMMTest testFeatureDisabled(all - featureAMMClawback); testAMMClawbackSpecificAmount(all); testAMMClawbackExceedBalance(all); - testAMMClawbackExceedBalance(all - fixAMMv1_3); testAMMClawbackAll(all); - testAMMClawbackAll(all - fixAMMv1_3); testAMMClawbackSameIssuerAssets(all); - testAMMClawbackSameIssuerAssets(all - fixAMMv1_3); testAMMClawbackSameCurrency(all); testAMMClawbackIssuesEachOther(all); testNotHoldingLptoken(all); testAssetFrozen(all); - testAssetFrozen(all - fixAMMv1_3); testSingleDepositAndClawback(all); - testSingleDepositAndClawback(all - fixAMMv1_3); } }; BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple); diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index d6142d7d3..16e66d803 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -1405,7 +1405,6 @@ private: using namespace jtx; FeatureBitset const all{supported_amendments()}; testRmFundedOffer(all); - testRmFundedOffer(all - fixAMMv1_3); testEnforceNoRipple(all); testFillModes(all); testOfferCrossWithXRP(all); @@ -1419,7 +1418,6 @@ private: testOfferCreateThenCross(all); testSellFlagExceedLimit(all); testGatewayCrossCurrency(all); - testGatewayCrossCurrency(all - fixAMMv1_3); testBridgedCross(all); testSellWithFillOrKill(all); testTransferRateOffer(all); @@ -1427,7 +1425,6 @@ private: testBadPathAssert(all); testSellFlagBasic(all); testDirectToDirectPath(all); - testDirectToDirectPath(all - fixAMMv1_3); testRequireAuth(all); testMissingAuth(all); } @@ -3838,9 +3835,7 @@ private: testBookStep(all); testBookStep(all | ownerPaysFee); testTransferRate(all | ownerPaysFee); - testTransferRate((all - fixAMMv1_3) | ownerPaysFee); testTransferRateNoOwnerFee(all); - testTransferRateNoOwnerFee(all - fixAMMv1_3); testLimitQuality(); testXRPPathLoop(); } @@ -3851,7 +3846,6 @@ private: using namespace jtx; FeatureBitset const all{supported_amendments()}; testStepLimit(all); - testStepLimit(all - fixAMMv1_3); } void @@ -3860,7 +3854,6 @@ private: using namespace jtx; FeatureBitset const all{supported_amendments()}; test_convert_all_of_an_asset(all); - test_convert_all_of_an_asset(all - fixAMMv1_3); } void diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 8d5595940..f5f21fb1f 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -833,10 +833,7 @@ private: // Tiny deposit testAMM( [&](AMM& ammAlice, Env& env) { - auto const enabledv1_3 = - env.current()->rules().enabled(fixAMMv1_3); - auto const err = - !enabledv1_3 ? ter(temBAD_AMOUNT) : ter(tesSUCCESS); + auto const err = ter(tesSUCCESS); // Pre-amendment XRP deposit side is rounded to 0 // and deposit fails. // Post-amendment XRP deposit side is rounded to 1 @@ -856,7 +853,7 @@ private: std::nullopt, 0, std::nullopt, - {features, features - fixAMMv1_3}); + {features}); // Invalid AMM testAMM([&](AMM& ammAlice, Env& env) { @@ -1322,15 +1319,6 @@ private: }); // Equal deposit limit, tokens rounded to 0 - testAMM( - [&](AMM& amm, Env& env) { - amm.deposit(DepositArg{ - .asset1In = STAmount{USD, 1, -15}, - .asset2In = XRPAmount{1}, - .err = ter(tecAMM_INVALID_TOKENS)}); - }, - {.pool = {{USD(1'000'000), XRP(1'000'000)}}, - .features = {features - fixAMMv1_3}}); testAMM([&](AMM& amm, Env& env) { amm.deposit(DepositArg{ .asset1In = STAmount{USD, 1, -15}, @@ -1573,7 +1561,7 @@ private: }); // Issuer create/deposit - for (auto const& feat : {all, all - fixAMMv1_3}) + for (auto const& feat : {all}) { Env env(*this, feat); env.fund(XRP(30000), gw); @@ -2003,22 +1991,20 @@ private: // while leaving a tiny amount in USD pool. // Post-amendment: // Most of the pool is withdrawn with remaining tiny amounts - auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) - : ter(tecAMM_BALANCE); + auto err = ter(tesSUCCESS); ammAlice.withdraw( alice, IOUAmount{9'999'999'9999, -4}, std::nullopt, std::nullopt, err); - if (env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(1), STAmount{USD, 1, -7}, IOUAmount{1, -4})); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(1), STAmount{USD, 1, -7}, IOUAmount{1, -4})); }, std::nullopt, 0, std::nullopt, - {all, all - fixAMMv1_3}); + {all}); testAMM( [&](AMM& ammAlice, Env& env) { @@ -2028,22 +2014,20 @@ private: // Equal withdraw but due to XRP precision limit, // this results in full withdraw of XRP pool only, // while leaving a tiny amount in USD pool. - auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) - : ter(tecAMM_BALANCE); + auto err = ter(tesSUCCESS); ammAlice.withdraw( alice, IOUAmount{9'999'999'999999999, -9}, std::nullopt, std::nullopt, err); - if (env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(1), STAmount{USD, 1, -11}, IOUAmount{1, -8})); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(1), STAmount{USD, 1, -11}, IOUAmount{1, -8})); }, std::nullopt, 0, std::nullopt, - {all, all - fixAMMv1_3}); + {all}); // Invalid AMM testAMM([&](AMM& ammAlice, Env& env) { @@ -2111,16 +2095,14 @@ private: testAMM( [&](AMM& ammAlice, Env& env) { ammAlice.deposit(carol, 1'000'000); - auto const err = env.enabled(fixAMMv1_3) - ? ter(tecAMM_INVALID_TOKENS) - : ter(tecAMM_FAILED); + auto const err = ter(tecAMM_INVALID_TOKENS); ammAlice.withdraw( carol, USD(100), std::nullopt, IOUAmount{500, 0}, err); }, std::nullopt, 0, std::nullopt, - {all, all - fixAMMv1_3}); + {all}); // Withdraw with EPrice limit. Fails to withdraw, calculated tokens // to withdraw are greater than the LP shares. @@ -2187,9 +2169,7 @@ private: // are rounded to all LP tokens. testAMM( [&](AMM& ammAlice, Env& env) { - auto const err = env.enabled(fixAMMv1_3) - ? ter(tecINVARIANT_FAILED) - : ter(tecAMM_BALANCE); + auto const err = ter(tecINVARIANT_FAILED); ammAlice.withdraw( alice, STAmount{USD, UINT64_C(9'999'999999999999), -12}, @@ -2197,7 +2177,7 @@ private: std::nullopt, err); }, - {.features = {all, all - fixAMMv1_3}, .noLog = true}); + {.features = {all}, .noLog = true}); // Tiny withdraw testAMM([&](AMM& ammAlice, Env&) { @@ -2305,21 +2285,15 @@ private: testAMM( [&](AMM& ammAlice, Env& env) { ammAlice.withdraw(alice, XRP(1'000)); - if (!env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(9'000), - USD(10'000), - IOUAmount{9'486'832'98050514, -8})); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{9'000'000'001}, - USD(10'000), - IOUAmount{9'486'832'98050514, -8})); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{9'000'000'001}, + USD(10'000), + IOUAmount{9'486'832'98050514, -8})); }, std::nullopt, 0, std::nullopt, - {all, all - fixAMMv1_3}); + {all}); // Single withdrawal by tokens 10000. testAMM([&](AMM& ammAlice, Env&) { @@ -2381,20 +2355,14 @@ private: ammAlice.withdraw(carol, lpTokens, USD(0)); lpTokens = ammAlice.deposit(carol, XRPAmount(1)); ammAlice.withdraw(carol, lpTokens, XRPAmount(0)); - if (!env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), USD(10'000), ammAlice.tokens())); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(10'000'000'001), - USD(10'000), - ammAlice.tokens())); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(10'000'000'001), USD(10'000), ammAlice.tokens())); BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); }, std::nullopt, 0, std::nullopt, - {all, all - fixAMMv1_3}); + {all}); // Single deposit by different accounts and then withdraw // in reverse. @@ -2445,20 +2413,14 @@ private: carol, USD(100), std::nullopt, IOUAmount{520, 0}); BEAST_EXPECT(ammAlice.expectLPTokens( carol, IOUAmount{153'846'15384616, -8})); - if (!env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(11'000'000'000), - STAmount{USD, UINT64_C(9'372'781065088769), -12}, - IOUAmount{10'153'846'15384616, -8})); - else if (env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(11'000'000'000), - STAmount{USD, UINT64_C(9'372'78106508877), -11}, - IOUAmount{10'153'846'15384616, -8})); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(11'000'000'000), + STAmount{USD, UINT64_C(9'372'78106508877), -11}, + IOUAmount{10'153'846'15384616, -8})); ammAlice.withdrawAll(carol); BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); }, - {.features = {all, all - fixAMMv1_3}, .noLog = true}); + {.features = {all}, .noLog = true}); // Withdraw with EPrice limit. AssetOut is 0. testAMM( @@ -2468,21 +2430,15 @@ private: carol, USD(0), std::nullopt, IOUAmount{520, 0}); BEAST_EXPECT(ammAlice.expectLPTokens( carol, IOUAmount{153'846'15384616, -8})); - if (!env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(11'000), - STAmount{USD, UINT64_C(9'372'781065088769), -12}, - IOUAmount{10'153'846'15384616, -8})); - else if (env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(11'000), - STAmount{USD, UINT64_C(9'372'78106508877), -11}, - IOUAmount{10'153'846'15384616, -8})); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), + STAmount{USD, UINT64_C(9'372'78106508877), -11}, + IOUAmount{10'153'846'15384616, -8})); }, std::nullopt, 0, std::nullopt, - {all, all - fixAMMv1_3}); + {all}); // IOU to IOU + transfer fee { @@ -2525,21 +2481,13 @@ private: [&](AMM& ammAlice, Env& env) { // Single XRP pool ammAlice.withdraw(alice, std::nullopt, XRPAmount{1}); - if (!env.enabled(fixAMMv1_3)) - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{9'999'999'999}, - USD(10'000), - IOUAmount{9'999'999'9995, -4})); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), - USD(10'000), - IOUAmount{9'999'999'9995, -4})); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), USD(10'000), IOUAmount{9'999'999'9995, -4})); }, std::nullopt, 0, std::nullopt, - {all, all - fixAMMv1_3}); + {all}); testAMM([&](AMM& ammAlice, Env&) { // Single USD pool ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10}); @@ -2679,8 +2627,7 @@ private: // in order to ensure AMM invariant sqrt(asset1 * asset2) >= tokens // fund just one USD higher in this case, which is enough for // deposit to succeed - if (env.enabled(fixAMMv1_3)) - ++fundUSD; + ++fundUSD; fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct); ammAlice.deposit(a, tokens); ammAlice.vote(a, 50 * (i + 1)); @@ -3092,14 +3039,10 @@ private: fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct); ammAlice.deposit(bob, 1'000'000); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0})); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{12'000'000'001}, - USD(12'000), - IOUAmount{12'000'000, 0})); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{12'000'000'001}, + USD(12'000), + IOUAmount{12'000'000, 0})); // Initial state. Pay bidMin. env(ammAlice.bid({.account = carol, .bidMin = 110})).close(); @@ -3131,16 +3074,10 @@ private: BEAST_EXPECT(ammAlice.expectAuctionSlot( 0, std::nullopt, IOUAmount{110})); // ~321.09 tokens burnt on bidding fees. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(12'000), - USD(12'000), - IOUAmount{11'999'678'91, -2})); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{12'000'000'001}, - USD(12'000), - IOUAmount{11'999'678'91, -2})); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{12'000'000'001}, + USD(12'000), + IOUAmount{11'999'678'91, -2})); }, std::nullopt, 0, @@ -3169,12 +3106,8 @@ private: auto const slotPrice = IOUAmount{5'200}; ammTokens -= slotPrice; BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice)); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), USD(13'000), ammTokens)); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'003}, USD(13'000), ammTokens)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, USD(13'000), ammTokens)); // Discounted trade for (int i = 0; i < 10; ++i) { @@ -3196,16 +3129,10 @@ private: env.balance(ed, USD) == STAmount(USD, UINT64_C(18'999'0057261184), -10)); // USD pool is slightly higher because of the fees. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), - STAmount(USD, UINT64_C(13'002'98282151422), -11), - ammTokens)); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'003}, - STAmount(USD, UINT64_C(13'002'98282151422), -11), - ammTokens)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, + STAmount(USD, UINT64_C(13'002'98282151422), -11), + ammTokens)); ammTokens = ammAlice.getLPTokensBalance(); // Trade with the fee for (int i = 0; i < 10; ++i) @@ -3217,80 +3144,44 @@ private: // carol, bob, ed. the discounted fee is 10 times less // than the trading fee. - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - env.balance(dan, USD) == - STAmount(USD, UINT64_C(19'490'05672274399), -11)); - else - BEAST_EXPECT( - env.balance(dan, USD) == - STAmount(USD, UINT64_C(19'490'05672274398), -11)); + BEAST_EXPECT( + env.balance(dan, USD) == + STAmount(USD, UINT64_C(19'490'05672274398), -11)); // USD pool gains more in dan's fees. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), - STAmount{USD, UINT64_C(13'012'92609877023), -11}, - ammTokens)); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'003}, - STAmount{USD, UINT64_C(13'012'92609877024), -11}, - ammTokens)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, + STAmount{USD, UINT64_C(13'012'92609877024), -11}, + ammTokens)); // Discounted fee payment ammAlice.deposit(carol, USD(100)); ammTokens = ammAlice.getLPTokensBalance(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(13'000), - STAmount{USD, UINT64_C(13'112'92609877023), -11}, - ammTokens)); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'003}, - STAmount{USD, UINT64_C(13'112'92609877024), -11}, - ammTokens)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'003}, + STAmount{USD, UINT64_C(13'112'92609877024), -11}, + ammTokens)); env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110))); env.close(); // carol pays 100000 drops in fees // 99900668XRP swapped in for 100USD - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'100'000'668}, - STAmount{USD, UINT64_C(13'012'92609877023), -11}, - ammTokens)); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'100'000'671}, - STAmount{USD, UINT64_C(13'012'92609877024), -11}, - ammTokens)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'100'000'671}, + STAmount{USD, UINT64_C(13'012'92609877024), -11}, + ammTokens)); // Payment with the trading fee env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110))); env.close(); // alice pays ~1.011USD in fees, which is ~10 times more // than carol's fee // 100.099431529USD swapped in for 100XRP - if (!features[fixAMMv1_3]) - { - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'668}, - STAmount{USD, UINT64_C(13'114'03663047269), -11}, - ammTokens)); - } - else - { - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'671}, - STAmount{USD, UINT64_C(13'114'03663044937), -11}, - ammTokens)); - } + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'671}, + STAmount{USD, UINT64_C(13'114'03663044937), -11}, + ammTokens)); + // Auction slot expired, no discounted fee env.close(seconds(TOTAL_TIME_SLOT_SECS + 1)); // clock is parent's based env.close(); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(29'399'00572620544), -11)); ammTokens = ammAlice.getLPTokensBalance(); for (int i = 0; i < 10; ++i) { @@ -3299,45 +3190,23 @@ private: } // carol pays ~9.94USD in fees, which is ~10 times more in // trading fees vs discounted fee. - if (!features[fixAMMv1_3]) - { - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(29'389'06197177124), -11)); - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'668}, - STAmount{USD, UINT64_C(13'123'98038490689), -11}, - ammTokens)); - } - else - { - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(29'389'06197177129), -11)); - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount{13'000'000'671}, - STAmount{USD, UINT64_C(13'123'98038488352), -11}, - ammTokens)); - } + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(29'389'06197177129), -11)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{13'000'000'671}, + STAmount{USD, UINT64_C(13'123'98038488352), -11}, + ammTokens)); + env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110))); env.close(); // carol pays ~1.008XRP in trading fee, which is // ~10 times more than the discounted fee. // 99.815876XRP is swapped in for 100USD - if (!features[fixAMMv1_3]) - { - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(13'100'824'790), - STAmount{USD, UINT64_C(13'023'98038490689), -11}, - ammTokens)); - } - else - { - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(13'100'824'793), - STAmount{USD, UINT64_C(13'023'98038488352), -11}, - ammTokens)); - } + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(13'100'824'793), + STAmount{USD, UINT64_C(13'023'98038488352), -11}, + ammTokens)); }, std::nullopt, 1'000, @@ -4819,10 +4688,7 @@ private: carol, USD(100), std::nullopt, IOUAmount{520, 0}); // carol withdraws ~1,443.44USD auto const balanceAfterWithdraw = [&]() { - if (!features[fixAMMv1_3]) - return STAmount(USD, UINT64_C(30'443'43891402714), -11); - else - return STAmount(USD, UINT64_C(30'443'43891402713), -11); + return STAmount(USD, UINT64_C(30'443'43891402713), -11); }(); BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw); // Set to original pool size @@ -4832,22 +4698,12 @@ private: ammAlice.vote(alice, 0); BEAST_EXPECT(ammAlice.expectTradingFee(0)); auto const tokensNoFee = ammAlice.withdraw(carol, deposit); - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(30'443'43891402716), -11)); - else - BEAST_EXPECT( - env.balance(carol, USD) == - STAmount(USD, UINT64_C(30'443'43891402713), -11)); + BEAST_EXPECT( + env.balance(carol, USD) == + STAmount(USD, UINT64_C(30'443'43891402713), -11)); // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee // LPTokens - if (!features[fixAMMv1_3]) - BEAST_EXPECT( - tokensNoFee == IOUAmount(746'579'80779912, -8)); - else - BEAST_EXPECT( - tokensNoFee == IOUAmount(746'579'80779911, -8)); + BEAST_EXPECT(tokensNoFee == IOUAmount(746'579'80779911, -8)); BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8)); }, std::nullopt, @@ -5109,40 +4965,24 @@ private: // Due to round off some accounts have a tiny gain, while // other have a tiny loss. The last account to withdraw // gets everything in the pool. - if (features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), - STAmount{USD, UINT64_C(10'000'0000000003), -10}, - IOUAmount{10'000'000})); - else - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), USD(10'000), IOUAmount{10'000'000})); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), + STAmount{USD, UINT64_C(10'000'0000000003), -10}, + IOUAmount{10'000'000})); BEAST_EXPECT(expectLine(env, ben, USD(1'500'000))); BEAST_EXPECT(expectLine(env, simon, USD(1'500'000))); BEAST_EXPECT(expectLine(env, chris, USD(1'500'000))); BEAST_EXPECT(expectLine(env, dan, USD(1'500'000))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); - else - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + BEAST_EXPECT(expectLine(env, carol, USD(30'000))); BEAST_EXPECT(expectLine(env, ed, USD(1'500'000))); BEAST_EXPECT(expectLine(env, paul, USD(1'500'000))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLine( - env, - nataly, - STAmount{USD, UINT64_C(1'500'000'000000005), -9})); - else - BEAST_EXPECT(expectLine(env, nataly, USD(1'500'000))); + BEAST_EXPECT(expectLine(env, nataly, USD(1'500'000))); ammAlice.withdrawAll(alice); BEAST_EXPECT(!ammAlice.ammExists()); - if (features[fixAMMv1_3]) - BEAST_EXPECT(expectLine( - env, - alice, - STAmount{USD, UINT64_C(30'000'0000000003), -10})); - else - BEAST_EXPECT(expectLine(env, alice, USD(30'000))); + BEAST_EXPECT(expectLine( + env, + alice, + STAmount{USD, UINT64_C(30'000'0000000003), -10})); // alice XRP balance is 30,000initial - 50 ammcreate fee - // 10drops fee BEAST_EXPECT(accountBalance(env, alice) == "29949999990"); @@ -5192,66 +5032,36 @@ private: ammAlice.withdrawAll(nataly, XRP(0)); } auto const baseFee = env.current()->fees().base.drops(); - if (!features[fixAMMv1_3]) - { - // No round off with XRP in this test - BEAST_EXPECT(ammAlice.expectBalances( - XRP(10'000), USD(10'000), IOUAmount{10'000'000})); - ammAlice.withdrawAll(alice); - BEAST_EXPECT(!ammAlice.ammExists()); - // 20,000 initial - (deposit+withdraw) * 10 - auto const xrpBalance = - (XRP(2'000'000) - txfee(env, 20)).getText(); - BEAST_EXPECT(accountBalance(env, ben) == xrpBalance); - BEAST_EXPECT(accountBalance(env, simon) == xrpBalance); - BEAST_EXPECT(accountBalance(env, chris) == xrpBalance); - BEAST_EXPECT(accountBalance(env, dan) == xrpBalance); - - // 30,000 initial - (deposit+withdraw) * 10 - BEAST_EXPECT( - accountBalance(env, carol) == - std::to_string(30'000'000'000 - 20 * baseFee)); - BEAST_EXPECT(accountBalance(env, ed) == xrpBalance); - BEAST_EXPECT(accountBalance(env, paul) == xrpBalance); - BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance); - // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee - BEAST_EXPECT( - accountBalance(env, alice) == - std::to_string(29'950'000'000 - baseFee)); - } - else - { - // post-amendment the rounding takes place to ensure - // AMM invariant - BEAST_EXPECT(ammAlice.expectBalances( - XRPAmount(10'000'000'080), - USD(10'000), - IOUAmount{10'000'000})); - ammAlice.withdrawAll(alice); - BEAST_EXPECT(!ammAlice.ammExists()); - auto const xrpBalance = - XRP(2'000'000) - txfee(env, 20) - drops(10); - auto const xrpBalanceText = xrpBalance.getText(); - BEAST_EXPECT(accountBalance(env, ben) == xrpBalanceText); - BEAST_EXPECT(accountBalance(env, simon) == xrpBalanceText); - BEAST_EXPECT(accountBalance(env, chris) == xrpBalanceText); - BEAST_EXPECT(accountBalance(env, dan) == xrpBalanceText); - BEAST_EXPECT( - accountBalance(env, carol) == - std::to_string(30'000'000'000 - 20 * baseFee - 10)); - BEAST_EXPECT( - accountBalance(env, ed) == - (xrpBalance + drops(2)).getText()); - BEAST_EXPECT( - accountBalance(env, paul) == - (xrpBalance + drops(3)).getText()); - BEAST_EXPECT( - accountBalance(env, nataly) == - (xrpBalance + drops(5)).getText()); - BEAST_EXPECT( - accountBalance(env, alice) == - std::to_string(29'950'000'000 - baseFee + 80)); - } + // post-amendment the rounding takes place to ensure + // AMM invariant + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(10'000'000'080), + USD(10'000), + IOUAmount{10'000'000})); + ammAlice.withdrawAll(alice); + BEAST_EXPECT(!ammAlice.ammExists()); + auto const xrpBalance = + XRP(2'000'000) - txfee(env, 20) - drops(10); + auto const xrpBalanceText = xrpBalance.getText(); + BEAST_EXPECT(accountBalance(env, ben) == xrpBalanceText); + BEAST_EXPECT(accountBalance(env, simon) == xrpBalanceText); + BEAST_EXPECT(accountBalance(env, chris) == xrpBalanceText); + BEAST_EXPECT(accountBalance(env, dan) == xrpBalanceText); + BEAST_EXPECT( + accountBalance(env, carol) == + std::to_string(30'000'000'000 - 20 * baseFee - 10)); + BEAST_EXPECT( + accountBalance(env, ed) == + (xrpBalance + drops(2)).getText()); + BEAST_EXPECT( + accountBalance(env, paul) == + (xrpBalance + drops(3)).getText()); + BEAST_EXPECT( + accountBalance(env, nataly) == + (xrpBalance + drops(5)).getText()); + BEAST_EXPECT( + accountBalance(env, alice) == + std::to_string(29'950'000'000 - baseFee + 80)); }, std::nullopt, 0, @@ -6374,7 +6184,7 @@ private: }) { testcase(input.testCase); - for (auto const& features : {all - fixAMMv1_3, all}) + for (auto const& features : {all}) { // Env env(*this, features, // std::make_unique(&logs)); @@ -6432,8 +6242,7 @@ private: auto const goodUsdGH = input.goodUsdGHr; auto const goodUsdBIT = input.goodUsdBITr; - auto const lpTokenBalance = - env.enabled(fixAMMv1_3) && input.lpTokenBalanceAlt + auto const lpTokenBalance = input.lpTokenBalanceAlt ? *input.lpTokenBalanceAlt : input.lpTokenBalance; @@ -6973,8 +6782,7 @@ private: }; test( [&](AMM& amm, Env& env) { - auto const err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) - : ter(tecUNFUNDED_AMM); + auto const err = ter(tesSUCCESS); amm.deposit(DepositArg{ .account = alice, .asset1In = amount, .err = err}); }, @@ -6986,10 +6794,7 @@ private: amm.withdraw(WithdrawArg{.asset1Out = STAmount{XPM, 1, -5}}); auto const [amount_, amount2_, lptAMM_] = amm.balances(XRP, XPM); - if (!env.enabled(fixAMMv1_3)) - BEAST_EXPECT((amount2 - amount2_) > withdraw); - else - BEAST_EXPECT((amount2 - amount2_) <= withdraw); + BEAST_EXPECT((amount2 - amount2_) <= withdraw); }, 0); } @@ -7003,8 +6808,7 @@ private: { auto const [amount, amount2, lptBalance] = amm.balances(GBP, EUR); - NumberRoundModeGuard g( - env.enabled(fixAMMv1_3) ? Number::upward : Number::getround()); + NumberRoundModeGuard g(Number::upward); auto const res = root2(amount * amount2); if (shouldFail) @@ -7046,7 +6850,7 @@ private: env, "dep1", deposit == STAmount{EUR, 1, -3} && - !env.enabled(fixAMMv1_3)); + !true /*env.enabled(fixAMMv1_3)*/); }, {{GBP(30'000), EUR(30'000)}}, 0, @@ -7107,7 +6911,7 @@ private: ammAlice, env, "dep3", - exponent != -3 && !env.enabled(fixAMMv1_3)); + exponent != -3 && !true /*env.enabled(fixAMMv1_3)*/); }, {{GBP(10'000), EUR(30'000)}}, 0, @@ -7329,51 +7133,35 @@ private: testFeeVote(); testInvalidBid(); testBid(all); - testBid(all - fixAMMv1_3); testInvalidAMMPayment(); testBasicPaymentEngine(all); - testBasicPaymentEngine(all - fixAMMv1_3); testBasicPaymentEngine(all - fixReducedOffersV2); - testBasicPaymentEngine(all - fixAMMv1_3 - fixReducedOffersV2); testAMMTokens(); testAmendment(); testFlags(); testRippling(); testAMMAndCLOB(all); - testAMMAndCLOB(all - fixAMMv1_3); testTradingFee(all); - testTradingFee(all - fixAMMv1_3); testAdjustedTokens(all); - testAdjustedTokens(all - fixAMMv1_3); testAutoDelete(); testClawback(); testAMMID(); testSelection(all); - testSelection(all - fixAMMv1_3); testFixDefaultInnerObj(); testMalformed(); testFixOverflowOffer(all); - testFixOverflowOffer(all - fixAMMv1_3); testSwapRounding(); testFixChangeSpotPriceQuality(all); - testFixChangeSpotPriceQuality(all - fixAMMv1_3); testFixAMMOfferBlockedByLOB(all); - testFixAMMOfferBlockedByLOB(all - fixAMMv1_3); testLPTokenBalance(all); - testLPTokenBalance(all - fixAMMv1_3); testAMMClawback(all); testAMMClawback(all - featureAMMClawback); - testAMMClawback(all - fixAMMv1_3 - featureAMMClawback); testAMMDepositWithFrozenAssets(all); testAMMDepositWithFrozenAssets(all - featureAMMClawback); - testAMMDepositWithFrozenAssets(all - fixAMMv1_3 - featureAMMClawback); testFixReserveCheckOnWithdrawal(all); testDepositAndWithdrawRounding(all); - testDepositAndWithdrawRounding(all - fixAMMv1_3); testDepositRounding(all); - testDepositRounding(all - fixAMMv1_3); testWithdrawRounding(all); - testWithdrawRounding(all - fixAMMv1_3); // testFailedPseudoAccount(); } }; diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index cbb815994..66a866ea0 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -42,12 +42,6 @@ number(STAmount const& a) IOUAmount AMM::initialTokens() { - if (!env_.enabled(fixAMMv1_3)) - { - auto const product = number(asset1_) * number(asset2_); - return (IOUAmount)(product.mantissa() >= 0 ? root2(product) - : root2(-product)); - } return getLPTokensBalance(); } diff --git a/src/test/rpc/AMMInfo_test.cpp b/src/test/rpc/AMMInfo_test.cpp index 98e62d892..6503de16a 100644 --- a/src/test/rpc/AMMInfo_test.cpp +++ b/src/test/rpc/AMMInfo_test.cpp @@ -218,10 +218,7 @@ public: { Account a(std::to_string(i)); votes.insert({a.human(), 50 * (i + 1)}); - if (!features[fixAMMv1_3]) - fund(env, gw, {a}, {USD(10000)}, Fund::Acct); - else - fund(env, gw, {a}, {USD(10001)}, Fund::Acct); + fund(env, gw, {a}, {USD(10001)}, Fund::Acct); ammAlice.deposit(a, 10000000); ammAlice.vote(a, 50 * (i + 1)); } @@ -231,22 +228,13 @@ public: env.fund(XRP(1000), bob, ed, bill); env(ammAlice.bid( {.bidMin = 100, .authAccounts = {carol, bob, ed, bill}})); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(ammAlice.expectAmmRpcInfo( - XRP(80000), - USD(80000), - IOUAmount{79994400}, - std::nullopt, - std::nullopt, - ammAlice.ammAccount())); - else - BEAST_EXPECT(ammAlice.expectAmmRpcInfo( - XRPAmount(80000000005), - STAmount{USD, UINT64_C(80'000'00000000005), -11}, - IOUAmount{79994400}, - std::nullopt, - std::nullopt, - ammAlice.ammAccount())); + BEAST_EXPECT(ammAlice.expectAmmRpcInfo( + XRPAmount(80000000005), + STAmount{USD, UINT64_C(80'000'00000000005), -11}, + IOUAmount{79994400}, + std::nullopt, + std::nullopt, + ammAlice.ammAccount())); for (auto i = 0; i < 2; ++i) { std::unordered_set authAccounts = { @@ -363,7 +351,6 @@ public: testErrors(); testSimpleRpc(); testVoteAndBid(all); - testVoteAndBid(all - fixAMMv1_3); testFreeze(); testInvalidAmmField(); } diff --git a/src/xrpld/app/misc/AMMHelpers.h b/src/xrpld/app/misc/AMMHelpers.h index 5825d6ad9..b5c257e33 100644 --- a/src/xrpld/app/misc/AMMHelpers.h +++ b/src/xrpld/app/misc/AMMHelpers.h @@ -590,13 +590,6 @@ getRoundedAsset( A const& frac, IsDeposit isDeposit) { - if (!rules.enabled(fixAMMv1_3)) - { - if constexpr (std::is_same_v) - return multiply(balance, frac, balance.issue()); - else - return toSTAmount(balance.issue(), balance * frac); - } auto const rm = detail::getAssetRounding(isDeposit); return multiply(balance, frac, rm); } diff --git a/src/xrpld/app/misc/detail/AMMHelpers.cpp b/src/xrpld/app/misc/detail/AMMHelpers.cpp index 97c355b1f..644482bcc 100644 --- a/src/xrpld/app/misc/detail/AMMHelpers.cpp +++ b/src/xrpld/app/misc/detail/AMMHelpers.cpp @@ -28,8 +28,7 @@ ammLPTokens( Issue const& lptIssue) { // AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance - auto const rounding = - isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround(); + auto const rounding = Number::downward; NumberRoundModeGuard g(rounding); auto const tokens = root2(asset1 * asset2); return toSTAmount(lptIssue, tokens); @@ -52,17 +51,10 @@ lpTokensOut( auto const f2 = feeMultHalf(tfee) / f1; Number const r = asset1Deposit / asset1Balance; auto const c = root2(f2 * f2 + r / f1) - f2; - if (!isFeatureEnabled(fixAMMv1_3)) - { - auto const t = lptAMMBalance * (r - c) / (1 + c); - return toSTAmount(lptAMMBalance.issue(), t); - } - else - { - // minimize tokens out - auto const frac = (r - c) / (1 + c); - return multiply(lptAMMBalance, frac, Number::downward); - } + + // minimize tokens out + auto const frac = (r - c) / (1 + c); + return multiply(lptAMMBalance, frac, Number::downward); } /* Equation 4 solves equation 3 for b: @@ -91,17 +83,10 @@ ammAssetIn( auto const a = 1 / (t2 * t2); auto const b = 2 * d / t2 - 1 / f1; auto const c = d * d - f2 * f2; - if (!isFeatureEnabled(fixAMMv1_3)) - { - return toSTAmount( - asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c)); - } - else - { - // maximize deposit - auto const frac = solveQuadraticEq(a, b, c); - return multiply(asset1Balance, frac, Number::upward); - } + + // maximize deposit + auto const frac = solveQuadraticEq(a, b, c); + return multiply(asset1Balance, frac, Number::upward); } /* Equation 7: @@ -118,17 +103,10 @@ lpTokensIn( Number const fr = asset1Withdraw / asset1Balance; auto const f1 = getFee(tfee); auto const c = fr * f1 + 2 - f1; - if (!isFeatureEnabled(fixAMMv1_3)) - { - auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2; - return toSTAmount(lptAMMBalance.issue(), t); - } - else - { - // maximize tokens in - auto const frac = (c - root2(c * c - 4 * fr)) / 2; - return multiply(lptAMMBalance, frac, Number::upward); - } + + // maximize tokens in + auto const frac = (c - root2(c * c - 4 * fr)) / 2; + return multiply(lptAMMBalance, frac, Number::upward); } /* Equation 8 solves equation 7 for b: @@ -150,17 +128,10 @@ ammAssetOut( { auto const f = getFee(tfee); Number const t1 = lpTokens / lptAMMBalance; - if (!isFeatureEnabled(fixAMMv1_3)) - { - auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); - return toSTAmount(assetBalance.issue(), b); - } - else - { - // minimize withdraw - auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); - return multiply(assetBalance, frac, Number::downward); - } + + // minimize withdraw + auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); + return multiply(assetBalance, frac, Number::downward); } Number @@ -194,48 +165,7 @@ adjustAmountsByLPTokens( IsDeposit isDeposit) { // AMMv1_3 amendment adjusts tokens and amounts in deposit/withdraw - if (isFeatureEnabled(fixAMMv1_3)) - return std::make_tuple(amount, amount2, lpTokens); - - auto const lpTokensActual = - adjustLPTokens(lptAMMBalance, lpTokens, isDeposit); - - if (lpTokensActual == beast::zero) - { - auto const amount2Opt = - amount2 ? std::make_optional(STAmount{}) : std::nullopt; - return std::make_tuple(STAmount{}, amount2Opt, lpTokensActual); - } - - if (lpTokensActual < lpTokens) - { - // Equal trade - if (amount2) - { - Number const fr = lpTokensActual / lpTokens; - auto const amountActual = toSTAmount(amount.issue(), fr * amount); - auto const amount2Actual = - toSTAmount(amount2->issue(), fr * *amount2); - return std::make_tuple(amountActual, amount2Actual, lpTokensActual); - } - - // Single trade - auto const amountActual = [&]() { - if (isDeposit == IsDeposit::Yes) - return ammAssetIn( - amountBalance, lptAMMBalance, lpTokensActual, tfee); - return ammAssetOut( - amountBalance, lptAMMBalance, lpTokensActual, tfee); - }(); - - return std::make_tuple(amountActual, std::nullopt, lpTokensActual); - } - - XRPL_ASSERT( - lpTokensActual == lpTokens, - "ripple::adjustAmountsByLPTokens : LP tokens match actual"); - - return {amount, amount2, lpTokensActual}; + return std::make_tuple(amount, amount2, lpTokens); } Number @@ -275,9 +205,6 @@ getRoundedAsset( std::function&& productCb, IsDeposit isDeposit) { - if (!rules.enabled(fixAMMv1_3)) - return toSTAmount(balance.issue(), noRoundCb()); - auto const rm = detail::getAssetRounding(isDeposit); if (isDeposit == IsDeposit::Yes) return multiply(balance, productCb(), rm); @@ -292,9 +219,6 @@ getRoundedLPTokens( Number const& frac, IsDeposit isDeposit) { - if (!rules.enabled(fixAMMv1_3)) - return toSTAmount(balance.issue(), balance * frac); - auto const rm = detail::getLPTokenRounding(isDeposit); auto const tokens = multiply(balance, frac, rm); return adjustLPTokens(balance, tokens, isDeposit); @@ -308,9 +232,6 @@ getRoundedLPTokens( std::function&& productCb, IsDeposit isDeposit) { - if (!rules.enabled(fixAMMv1_3)) - return toSTAmount(lptAMMBalance.issue(), noRoundCb()); - auto const tokens = [&] { auto const rm = detail::getLPTokenRounding(isDeposit); if (isDeposit == IsDeposit::Yes) @@ -332,8 +253,6 @@ adjustAssetInByTokens( STAmount const& tokens, std::uint16_t tfee) { - if (!rules.enabled(fixAMMv1_3)) - return {tokens, amount}; auto assetAdj = ammAssetIn(balance, lptAMMBalance, tokens, tfee); auto tokensAdj = tokens; // Rounding didn't work the right way. @@ -358,8 +277,6 @@ adjustAssetOutByTokens( STAmount const& tokens, std::uint16_t tfee) { - if (!rules.enabled(fixAMMv1_3)) - return {tokens, amount}; auto assetAdj = ammAssetOut(balance, lptAMMBalance, tokens, tfee); auto tokensAdj = tokens; // Rounding didn't work the right way. @@ -382,8 +299,6 @@ adjustFracByTokens( STAmount const& tokens, Number const& frac) { - if (!rules.enabled(fixAMMv1_3)) - return frac; return tokens / lptAMMBalance; } diff --git a/src/xrpld/app/tx/detail/AMMBid.cpp b/src/xrpld/app/tx/detail/AMMBid.cpp index 9a9730228..ee8943276 100644 --- a/src/xrpld/app/tx/detail/AMMBid.cpp +++ b/src/xrpld/app/tx/detail/AMMBid.cpp @@ -79,7 +79,7 @@ AMMBid::preflight(PreflightContext const& ctx) JLOG(ctx.j.debug()) << "AMM Bid: Invalid number of AuthAccounts."; return temMALFORMED; } - else if (ctx.rules.enabled(fixAMMv1_3)) + else { AccountID account = ctx.tx[sfAccount]; std::set unique; diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 50152e401..0dab0d68b 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -634,8 +634,6 @@ adjustLPTokensOut( STAmount const& lptAMMBalance, STAmount const& lpTokensDeposit) { - if (!rules.enabled(fixAMMv1_3)) - return lpTokensDeposit; return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes); } @@ -658,7 +656,7 @@ AMMDeposit::equalDepositTokens( { auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; auto const frac = divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue()); @@ -735,10 +733,7 @@ AMMDeposit::equalDepositLimit( getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes); if (tokensAdj == beast::zero) { - if (!view.rules().enabled(fixAMMv1_3)) - return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE - else - return {tecAMM_INVALID_TOKENS, STAmount{}}; + return {tecAMM_INVALID_TOKENS, STAmount{}}; } // factor in the adjusted tokens frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); @@ -762,10 +757,7 @@ AMMDeposit::equalDepositLimit( getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes); if (tokensAdj == beast::zero) { - if (!view.rules().enabled(fixAMMv1_3)) - return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE - else - return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE + return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE } // factor in the adjusted tokens frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); @@ -811,15 +803,12 @@ AMMDeposit::singleDeposit( lpTokensOut(amountBalance, amount, lptAMMBalance, tfee)); if (tokens == beast::zero) { - if (!view.rules().enabled(fixAMMv1_3)) - return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE - else - return {tecAMM_INVALID_TOKENS, STAmount{}}; + return {tecAMM_INVALID_TOKENS, STAmount{}}; } // factor in the adjusted tokens auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens( view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE return deposit( view, @@ -854,7 +843,7 @@ AMMDeposit::singleDepositTokens( { auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // the adjusted tokens are factored in auto const amountDeposit = @@ -918,15 +907,12 @@ AMMDeposit::singleDepositEPrice( lpTokensOut(amountBalance, amount, lptAMMBalance, tfee)); if (tokens <= beast::zero) { - if (!view.rules().enabled(fixAMMv1_3)) - return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE - else - return {tecAMM_INVALID_TOKENS, STAmount{}}; + return {tecAMM_INVALID_TOKENS, STAmount{}}; } // factor in the adjusted tokens auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens( view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE auto const ep = Number{amountDepositAdj} / tokensAdj; if (ep <= ePrice) @@ -988,7 +974,7 @@ AMMDeposit::singleDepositEPrice( lptAMMBalance, tokens, tfee); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE return deposit( diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 233ebb457..e8569b48c 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -689,7 +689,7 @@ adjustLPTokensIn( STAmount const& lpTokensWithdraw, WithdrawAll withdrawAll) { - if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes) + if (withdrawAll == WithdrawAll::Yes) return lpTokensWithdraw; return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No); } @@ -801,7 +801,7 @@ AMMWithdraw::equalWithdrawTokens( auto const tokensAdj = adjustLPTokensIn( view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return { tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt}; // the adjusted tokens are factored in @@ -885,7 +885,7 @@ AMMWithdraw::equalWithdrawLimit( getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No); auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // factor in the adjusted tokens frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); @@ -910,21 +910,13 @@ AMMWithdraw::equalWithdrawLimit( getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No); tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE // factor in the adjusted tokens frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No); - if (!view.rules().enabled(fixAMMv1_3)) - { - // LCOV_EXCL_START - XRPL_ASSERT( - amountWithdraw <= amount, - "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw"); - // LCOV_EXCL_STOP - } - else if (amountWithdraw > amount) + if (amountWithdraw > amount) return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE return withdraw( view, @@ -960,15 +952,12 @@ AMMWithdraw::singleWithdraw( isWithdrawAll(ctx_.tx)); if (tokens == beast::zero) { - if (!view.rules().enabled(fixAMMv1_3)) - return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE - else - return {tecAMM_INVALID_TOKENS, STAmount{}}; + return {tecAMM_INVALID_TOKENS, STAmount{}}; } // factor in the adjusted tokens auto const [tokensAdj, amountWithdrawAdj] = adjustAssetOutByTokens( view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE return withdraw( view, @@ -1005,7 +994,7 @@ AMMWithdraw::singleWithdrawTokens( { auto const tokensAdj = adjustLPTokensIn( view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx)); - if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) + if (tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // the adjusted tokens are factored in auto const amountWithdraw = @@ -1080,10 +1069,7 @@ AMMWithdraw::singleWithdrawEPrice( view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No); if (tokensAdj <= beast::zero) { - if (!view.rules().enabled(fixAMMv1_3)) - return {tecAMM_FAILED, STAmount{}}; - else - return {tecAMM_INVALID_TOKENS, STAmount{}}; + return {tecAMM_INVALID_TOKENS, STAmount{}}; } auto amtNoRoundCb = [&] { return tokensAdj / ePrice; }; auto amtProdCb = [&] { return tokensAdj / ePrice; }; diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 9484eefd7..85b899b90 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -1954,7 +1954,7 @@ ValidAMM::finalize( if (result != tesSUCCESS && result != tecINCOMPLETE) return true; - bool const enforce = view.rules().enabled(fixAMMv1_3); + bool const enforce = true; // view.rules().enabled(fixAMMv1_3); switch (tx.getTxnType()) { diff --git a/src/xrpld/app/tx/detail/Offer.h b/src/xrpld/app/tx/detail/Offer.h index a26a35e60..0ebe5c08c 100644 --- a/src/xrpld/app/tx/detail/Offer.h +++ b/src/xrpld/app/tx/detail/Offer.h @@ -173,9 +173,6 @@ public: bool checkInvariant(TAmounts const& consumed, beast::Journal j) const { - if (!isFeatureEnabled(fixAMMv1_3)) - return true; - if (consumed.in > m_amounts.in || consumed.out > m_amounts.out) { // LCOV_EXCL_START From 131d659032c637560dcfbdcdafe7e6ca29c8b13a Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Fri, 11 Jul 2025 16:03:28 -0400 Subject: [PATCH 03/18] fixAMMClawbackRounding: adjust last holder's LPToken balance (#5513) Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance. This was fixed for `AMMWithdraw` in `fixAMMv1_1` by adjusting the LPTokenBalance to be the same as the trustline balance. Since `AMMClawback` is also performing a withdrawal, we need to adjust LPTokenBalance as well in `AMMClawback.` This change includes: 1. Refactored `verifyAndAdjustLPTokenBalance` function in `AMMUtils`, which both`AMMWithdraw` and `AMMClawback` call to adjust LPTokenBalance. 2. Added the unit test `testLastHolderLPTokenBalance` to test the scenario. 3. Modify the existing unit tests for `fixAMMClawbackRounding`. --- include/xrpl/protocol/detail/features.macro | 1 + src/test/app/AMMClawback_test.cpp | 791 ++++++++++++++++---- src/xrpld/app/misc/AMMUtils.h | 11 + src/xrpld/app/misc/detail/AMMUtils.cpp | 30 + src/xrpld/app/tx/detail/AMMClawback.cpp | 53 +- src/xrpld/app/tx/detail/AMMWithdraw.cpp | 17 +- 6 files changed, 726 insertions(+), 177 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 3f48ac714..738a8287b 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -31,6 +31,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 4d2a99593..4fec924f9 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -16,27 +16,25 @@ //============================================================================== #include #include -#include -#include -#include -#include -#include +#include + +#include + #include -#include -#include + namespace ripple { namespace test { -class AMMClawback_test : public jtx::AMMTest +class AMMClawback_test : public beast::unit_test::suite { void - testInvalidRequest(FeatureBitset features) + testInvalidRequest() { testcase("test invalid request"); using namespace jtx; // Test if holder does not exist. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(100000), gw, alice); @@ -47,8 +45,9 @@ class AMMClawback_test : public jtx::AMMTest env.close(); env.require(flags(gw, asfAllowTrustLineClawback)); + auto const USD = gw["USD"]; env.trust(USD(10000), alice); - env(pay(gw, alice, gw["USD"](100))); + env(pay(gw, alice, USD(100))); AMM amm(env, alice, XRP(100), USD(100)); env.close(); @@ -61,7 +60,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if asset pair provided does not exist. This should // return terNO_AMM error. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(100000), gw, alice); @@ -87,14 +86,14 @@ class AMMClawback_test : public jtx::AMMTest // The AMM account does not exist at all now. // It should return terNO_AMM error. - env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + env(amm::ammClawback(gw, alice, USD, gw["EUR"], std::nullopt), ter(terNO_AMM)); } // Test if the issuer field and holder field is the same. This should // return temMALFORMED error. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -124,7 +123,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the Asset field matches the Account field. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -156,7 +155,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the Amount field matches the Asset field. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -189,7 +188,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the Amount is invalid, which is less than zero. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -230,7 +229,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback // transaction is prohibited. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -241,7 +240,7 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(1000), alice); env(pay(gw, alice, USD(100))); env.close(); - env.require(balance(alice, gw["USD"](100))); + env.require(balance(alice, USD(100))); env.require(balance(gw, alice["USD"](-100))); // gw creates AMM pool of XRP/USD. @@ -255,7 +254,7 @@ class AMMClawback_test : public jtx::AMMTest // Test invalid flag. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -283,7 +282,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if tfClawTwoAssets is set when the two assets in the AMM pool // are not issued by the same issuer. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -314,7 +313,7 @@ class AMMClawback_test : public jtx::AMMTest // Test clawing back XRP is being prohibited. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(1000000), gw, alice); @@ -400,7 +399,7 @@ class AMMClawback_test : public jtx::AMMTest env(pay(gw, alice, USD(3000))); env.close(); env.require(balance(gw, alice["USD"](-3000))); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; @@ -408,7 +407,7 @@ class AMMClawback_test : public jtx::AMMTest env(pay(gw2, alice, EUR(3000))); env.close(); env.require(balance(gw2, alice["EUR"](-3000))); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -426,13 +425,13 @@ class AMMClawback_test : public jtx::AMMTest // USD into the pool, then she has 1000 USD. And 1000 USD was clawed // back from the AMM pool, so she still has 1000 USD. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // Alice's initial balance for EUR is 3000 EUR. Alice deposited 1000 // EUR into the pool, 500 EUR was withdrawn proportionally. So she // has 2500 EUR now. env.require(balance(gw2, alice["EUR"](-2500))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, EUR(2500))); // 1000 USD and 500 EUR was withdrawn from the AMM pool, so the // current balance is 1000 USD and 500 EUR. @@ -452,12 +451,12 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 1000 USD because gw clawed back from the // AMM pool. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // Alice should has 3000 EUR now because another 500 EUR was // withdrawn. env.require(balance(gw2, alice["EUR"](-3000))); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // amm is automatically deleted. BEAST_EXPECT(!amm.ammExists()); @@ -483,7 +482,7 @@ class AMMClawback_test : public jtx::AMMTest env(pay(gw, alice, USD(3000))); env.close(); env.require(balance(gw, alice["USD"](-3000))); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // Alice creates AMM pool of XRP/USD. AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS)); @@ -503,11 +502,12 @@ class AMMClawback_test : public jtx::AMMTest // USD into the pool, then she has 1000 USD. And 1000 USD was clawed // back from the AMM pool, so she still has 1000 USD. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // Alice will get 500 XRP back. BEAST_EXPECT( expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500))); + aliceXrpBalance = env.balance(alice, XRP); // 1000 USD and 500 XRP was withdrawn from the AMM pool, so the // current balance is 1000 USD and 500 XRP. @@ -527,11 +527,11 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 1000 USD because gw clawed back from the // AMM pool. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); - // Alice will get another 1000 XRP back. + // Alice will get another 500 XRP back. BEAST_EXPECT( - expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500))); // amm is automatically deleted. BEAST_EXPECT(!amm.ammExists()); @@ -568,14 +568,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(6000))); env.close(); - env.require(balance(alice, gw["USD"](6000))); + env.require(balance(alice, USD(6000))); // gw2 issues 6000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(6000))); env.close(); - env.require(balance(alice, gw2["EUR"](6000))); + env.require(balance(alice, EUR(6000))); // Alice creates AMM pool of EUR/USD AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); @@ -592,12 +592,12 @@ class AMMClawback_test : public jtx::AMMTest // Alice's initial balance for USD is 6000 USD. Alice deposited 4000 // USD into the pool, then she has 2000 USD. And 1000 USD was clawed // back from the AMM pool, so she still has 2000 USD. - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000 // EUR into the pool, 1250 EUR was withdrawn proportionally. So she // has 2500 EUR now. - env.require(balance(alice, gw2["EUR"](2250))); + env.require(balance(alice, EUR(2250))); // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the // current balance is 3000 USD and 3750 EUR. @@ -615,7 +615,7 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 2000 USD because gw clawed back from the // AMM pool. - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); BEAST_EXPECT(amm.expectBalances( USD(2500), EUR(3125), IOUAmount{2795084971874737, -12})); @@ -627,12 +627,23 @@ class AMMClawback_test : public jtx::AMMTest env.close(); // Another 1 USD / 1.25 EUR was withdrawn. - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); - BEAST_EXPECT(amm.expectBalances( - USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12})); + else + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2499000000000001), -12}, + STAmount{EUR, UINT64_C(3123750000000001), -12}, + IOUAmount{2793966937885988, -12})); - BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25)); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25)); + else + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(2876'249999999999), -12)); // gw clawback 4000 USD, exceeding the current balance. We // will clawback all. @@ -640,7 +651,7 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); // All alice's EUR in the pool goes back to alice. BEAST_EXPECT( @@ -707,6 +718,7 @@ class AMMClawback_test : public jtx::AMMTest AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS)); BEAST_EXPECT(amm2.expectBalances( EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); + amm2.deposit(alice, EUR(1000), XRP(3000)); BEAST_EXPECT(amm2.expectBalances( EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); @@ -726,19 +738,36 @@ class AMMClawback_test : public jtx::AMMTest // Alice's initial balance for USD is 6000 USD. Alice deposited 1000 // USD into the pool, then she has 5000 USD. And 500 USD was clawed // back from the AMM pool, so she still has 5000 USD. - env.require(balance(alice, gw["USD"](5000))); + env.require(balance(alice, USD(5000))); // Bob's balance is not changed. - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(bob, USD(4000))); // Alice gets 1000 XRP back. - BEAST_EXPECT( - expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); + if (features[fixAMMClawbackRounding]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000) - XRPAmount(1))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000))); + aliceXrpBalance = env.balance(alice, XRP); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(2500), XRP(5000), IOUAmount{3535533905932737, -9})); + else + BEAST_EXPECT(amm.expectBalances( + USD(2500), + XRPAmount(5000000001), + IOUAmount{3'535'533'905932738, -9})); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{7071067811865474, -10})); + else + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); - BEAST_EXPECT(amm.expectBalances( - USD(2500), XRP(5000), IOUAmount{3535533905932737, -9})); - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{7071067811865474, -10})); BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9})); @@ -746,32 +775,62 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](5000))); - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(alice, USD(5000))); + env.require(balance(bob, USD(4000))); // Bob gets 20 XRP back. BEAST_EXPECT( expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20))); - BEAST_EXPECT(amm.expectBalances( - USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9})); - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{7071067811865474, -10})); - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); + bobXrpBalance = env.balance(bob, XRP); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9})); + else + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2490000000000001), -12}, + XRPAmount(4980000001), + IOUAmount{3521391'770309008, -9})); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectLPTokens( + alice, IOUAmount{7071067811865474, -10})); + else + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); + else + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); // gw2 clawback 200 EUR from amm2. env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)), ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw2["EUR"](4000))); - env.require(balance(bob, gw2["EUR"](3000))); + env.require(balance(alice, EUR(4000))); + env.require(balance(bob, EUR(3000))); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(600))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(600) - XRPAmount{1})); + aliceXrpBalance = env.balance(alice, XRP); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), + XRPAmount(8400000001), + IOUAmount{4849742261192856, -9})); - // Alice gets 600 XRP back. - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(1000) + XRP(600))); - BEAST_EXPECT(amm2.expectBalances( - EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9})); BEAST_EXPECT( amm2.expectLPTokens(alice, IOUAmount{1385640646055102, -9})); BEAST_EXPECT( @@ -784,22 +843,36 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](5000))); - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(alice, USD(5000))); + env.require(balance(bob, USD(4000))); // Alice gets 1000 XRP back. - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - - XRPAmount{1})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000) - XRPAmount{1})); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000))); + aliceXrpBalance = env.balance(alice, XRP); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); - BEAST_EXPECT(amm.expectBalances( - USD(1'990), - XRPAmount{3'980'000'001}, - IOUAmount{2814284989122459, -9})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); + else + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(1'990), + XRPAmount{3'980'000'001}, + IOUAmount{2814284989122459, -9})); + else + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(1990000000000001), -12}, + XRPAmount{3'980'000'001}, + IOUAmount{2814284989122460, -9})); // gw clawback 1000 USD from bob in amm, which also exceeds bob's // balance in amm. All bob's lptoken in amm will be consumed, which @@ -808,16 +881,14 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](5000))); - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(alice, USD(5000))); + env.require(balance(bob, USD(4000))); - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - - XRPAmount{1})); - BEAST_EXPECT(expectLedgerEntryRoot( - env, bob, bobXrpBalance + XRP(20) + XRP(1980))); + BEAST_EXPECT(expectLedgerEntryRoot(env, alice, aliceXrpBalance)); + + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(1980))); + bobXrpBalance = env.balance(bob, XRP); // Now neither alice nor bob has any lptoken in amm. BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); @@ -830,24 +901,28 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw2["EUR"](4000))); - env.require(balance(bob, gw2["EUR"](3000))); + env.require(balance(alice, EUR(4000))); + env.require(balance(bob, EUR(3000))); // Alice gets another 2400 XRP back, bob's XRP balance remains the // same. - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + XRP(2400) - - XRPAmount{1})); - BEAST_EXPECT(expectLedgerEntryRoot( - env, bob, bobXrpBalance + XRP(20) + XRP(1980))); + BEAST_EXPECT( + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(2400))); + + BEAST_EXPECT(expectLedgerEntryRoot(env, bob, bobXrpBalance)); + aliceXrpBalance = env.balance(alice, XRP); // Alice now does not have any lptoken in amm2 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), + XRPAmount(6000000001), + IOUAmount{3464101615137754, -9})); // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's // balance. All bob's lptokens will be consumed, which corresponds @@ -856,25 +931,29 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw2["EUR"](4000))); - env.require(balance(bob, gw2["EUR"](3000))); + env.require(balance(alice, EUR(4000))); + env.require(balance(bob, EUR(3000))); // Bob gets another 3000 XRP back. Alice's XRP balance remains the // same. - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + XRP(2400) - - XRPAmount{1})); - BEAST_EXPECT(expectLedgerEntryRoot( - env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000))); + BEAST_EXPECT(expectLedgerEntryRoot(env, alice, aliceXrpBalance)); + + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(3000))); + bobXrpBalance = env.balance(bob, XRP); // Neither alice nor bob has any lptoken in amm2 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0))); - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); + else + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), + XRPAmount(3000000001), + IOUAmount{1732050807568877, -9})); } } @@ -948,12 +1027,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT( amm.expectLPTokens(carol, IOUAmount{1118033988749894, -12})); - env.require(balance(alice, gw["USD"](2000))); - env.require(balance(alice, gw2["EUR"](1000))); - env.require(balance(bob, gw["USD"](3000))); - env.require(balance(bob, gw2["EUR"](2500))); - env.require(balance(carol, gw["USD"](3000))); - env.require(balance(carol, gw2["EUR"](2750))); + env.require(balance(alice, USD(2000))); + env.require(balance(alice, EUR(1000))); + env.require(balance(bob, USD(3000))); + env.require(balance(bob, EUR(2500))); + env.require(balance(carol, USD(3000))); + env.require(balance(carol, EUR(2750))); // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR) env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt), @@ -972,8 +1051,8 @@ class AMMClawback_test : public jtx::AMMTest amm.expectLPTokens(carol, IOUAmount{1118033988749894, -12})); // Bob will get 2500 EUR back. - env.require(balance(alice, gw["USD"](2000))); - env.require(balance(alice, gw2["EUR"](1000))); + env.require(balance(alice, USD(2000))); + env.require(balance(alice, EUR(1000))); BEAST_EXPECT( env.balance(bob, USD) == STAmount(USD, UINT64_C(3000000000000000), -12)); @@ -981,8 +1060,8 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT( env.balance(bob, EUR) == STAmount(EUR, UINT64_C(4999999999999999), -12)); - env.require(balance(carol, gw["USD"](3000))); - env.require(balance(carol, gw2["EUR"](2750))); + env.require(balance(carol, USD(3000))); + env.require(balance(carol, EUR(2750))); // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR) env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt), @@ -1003,8 +1082,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(carol, gw2["EUR"](2750))); - env.require(balance(carol, gw["USD"](4000))); + env.require(balance(carol, EUR(2750))); + env.require(balance(carol, USD(4000))); BEAST_EXPECT(!amm.ammExists()); } @@ -1351,11 +1430,20 @@ class AMMClawback_test : public jtx::AMMTest // gw claws back 1000 USD from gw2. env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(5000), EUR(10000), IOUAmount{7071067811865475, -12})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(5000), EUR(10000), IOUAmount{7071067811865475, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(5000), EUR(10000), IOUAmount{7071067811865474, -12})); BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12})); - BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1367,11 +1455,29 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 1000 EUR from gw. env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(4500), EUR(9000), IOUAmount{6363961030678928, -12})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(4500), EUR(9000), IOUAmount{6363961030678928, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(4500), + STAmount(EUR, UINT64_C(9000000000000001), -12), + IOUAmount{6363961030678927, -12})); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); - BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); - BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1383,11 +1489,28 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 4000 EUR from alice. env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(2500), EUR(5000), IOUAmount{3535533905932738, -12})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(2500), EUR(5000), IOUAmount{3535533905932738, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(2500), + STAmount(EUR, UINT64_C(5000000000000001), -12), + IOUAmount{3535533905932737, -12})); - BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); - BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + + if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12})); @@ -1454,14 +1577,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(3000))); env.close(); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(3000))); env.close(); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -1479,8 +1602,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(2500))); BEAST_EXPECT(amm.expectBalances( USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); @@ -1496,8 +1619,8 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 1000 USD because gw clawed back from the // AMM pool. - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(3000))); // amm is automatically deleted. BEAST_EXPECT(!amm.ammExists()); @@ -1522,14 +1645,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(3000))); env.close(); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(3000))); env.close(); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -1548,8 +1671,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(2500))); BEAST_EXPECT(amm.expectBalances( USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); BEAST_EXPECT( @@ -1575,14 +1698,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(3000))); env.close(); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(3000))); env.close(); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -1600,8 +1723,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(2500))); BEAST_EXPECT(amm.expectBalances( USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); BEAST_EXPECT( @@ -1728,10 +1851,17 @@ class AMMClawback_test : public jtx::AMMTest { testcase("test single depoit and clawback"); using namespace jtx; + std::string logs; // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back // to the holder. - Env env(*this, features); + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(1000000000), gw, alice); @@ -1747,7 +1877,7 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(1000))); env.close(); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // gw creates AMM pool of XRP/USD. AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS)); @@ -1775,21 +1905,362 @@ class AMMClawback_test : public jtx::AMMTest env, alice, aliceXrpBalance + XRP(29.289321))); } + void + testLastHolderLPTokenBalance(FeatureBitset features) + { + testcase( + "test last holder's lptoken balance not equal to AMM's lptoken " + "balance before clawback"); + using namespace jtx; + std::string logs; + + auto setupAccounts = + [&](Env& env, Account& gw, Account& alice, Account& bob) { + env.fund(XRP(100000), gw, alice, bob); + env.close(); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(40000))); + env.close(); + + return USD; + }; + + auto getLPTokenBalances = + [&](auto& env, + auto const& amm, + auto const& account) -> std::pair { + auto const lpToken = + getAccountLines( + env, account, amm.lptIssue())[jss::lines][0u][jss::balance] + .asString(); + auto const lpTokenBalance = + amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value] + .asString(); + return {lpToken, lpTokenBalance}; + }; + + // IOU/XRP pool. AMMClawback almost last holder's USD balance + { + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + AMM amm(env, alice, XRP(2), USD(1)); + amm.deposit(alice, IOUAmount{1'876123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000'000}); + amm.withdraw(alice, IOUAmount{1'876123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1414.21356237366" && + lpTokenBalance == "1414.213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (!features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, XRP, USD(1)), + ter(tecAMM_BALANCE)); + BEAST_EXPECT(amm.ammExists()); + } + else + { + auto const lpBalance = IOUAmount{989, -12}; + env(amm::ammClawback(gw, alice, USD, XRP, USD(1))); + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(7000000000000000), -28), + XRPAmount(1), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); + } + } + + // IOU/XRP pool. AMMClawback part of last holder's USD balance + { + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + AMM amm(env, alice, XRP(2), USD(1)); + amm.deposit(alice, IOUAmount{1'876123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000'000}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1416.08968586066" && + lpTokenBalance == "1416.089685861"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + env(amm::ammClawback(gw, alice, USD, XRP, USD(0.5))); + + if (!features[fixAMMClawbackRounding]) + { + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5013266196407), -13), + XRPAmount(1002654), + IOUAmount{708'9829046744941, -13})); + } + else + { + auto const lpBalance = IOUAmount{708'9829046743238, -13}; + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5013266196406999), -16), + XRPAmount(1002655), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); + } + } + + // IOU/XRP pool. AMMClawback all of last holder's USD balance + { + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + AMM amm(env, alice, XRP(2), USD(1)); + amm.deposit(alice, IOUAmount{1'876123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000'000}); + amm.withdraw(alice, IOUAmount{1'876123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1414.21356237366" && + lpTokenBalance == "1414.213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (!features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt)); + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(2410000000000000), -28), + XRPAmount(1), + IOUAmount{34, -11})); + } + else + { + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); + } + } + + // IOU/IOU pool, different issuers + { + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + Account gw2{"gateway2"}; + env.fund(XRP(100000), gw2); + env.close(); + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(50000))); + env.trust(EUR(100000), bob); + env(pay(gw2, bob, EUR(50000))); + env.close(); + + AMM amm(env, alice, USD(2), EUR(1)); + amm.deposit(alice, IOUAmount{1'576123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000}); + amm.withdraw(alice, IOUAmount{1'576123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1.414213562374011" && + lpTokenBalance == "1.414213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); + } + else + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + ter(tecINTERNAL)); + BEAST_EXPECT(amm.ammExists()); + } + } + + // IOU/IOU pool, same issuer + { + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + auto const EUR = gw["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw, alice, EUR(50000))); + env.trust(EUR(100000), bob); + env(pay(gw, bob, EUR(50000))); + env.close(); + + AMM amm(env, alice, USD(1), EUR(2)); + amm.deposit(alice, IOUAmount{1'076123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000}); + amm.withdraw(alice, IOUAmount{1'076123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1.414213562374011" && + lpTokenBalance == "1.414213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + txflags(tfClawTwoAssets)); + BEAST_EXPECT(!amm.ammExists()); + } + else + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + txflags(tfClawTwoAssets), + ter(tecINTERNAL)); + BEAST_EXPECT(amm.ammExists()); + } + } + + // IOU/IOU pool, larger asset ratio + { + // Env env(*this, features, std::make_unique(&logs)); + Env env( + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + auto const EUR = gw["EUR"]; + env.trust(EUR(1000000000), alice); + env(pay(gw, alice, EUR(500000000))); + env.trust(EUR(1000000000), bob); + env(pay(gw, bob, EUR(500000000))); + env.close(); + + AMM amm(env, alice, USD(1), EUR(2000000)); + amm.deposit(alice, IOUAmount{1'076123487565916, -12}); + amm.deposit(bob, IOUAmount{10000}); + amm.withdraw(alice, IOUAmount{1'076123487565916, -12}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + + BEAST_EXPECT( + lpToken == "1414.213562373101" && + lpTokenBalance == "1414.2135623731"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (!features[fixAMMClawbackRounding]) + { + // sqrt(amount * amount2) >= LPTokens and exceeds the allowed + // tolerance + env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), + ter(tecINVARIANT_FAILED)); + BEAST_EXPECT(amm.ammExists()); + } + else + { + env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), + txflags(tfClawTwoAssets)); + auto const lpBalance = IOUAmount{5, -12}; + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(4), -15), + STAmount(EUR, UINT64_C(8), -9), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); + } + } + } + void run() override { - FeatureBitset const all{jtx::supported_amendments()}; - testInvalidRequest(all); + FeatureBitset const all{ + jtx::supported_amendments() | fixAMMClawbackRounding}; + + testInvalidRequest(); testFeatureDisabled(all - featureAMMClawback); - testAMMClawbackSpecificAmount(all); - testAMMClawbackExceedBalance(all); - testAMMClawbackAll(all); - testAMMClawbackSameIssuerAssets(all); - testAMMClawbackSameCurrency(all); - testAMMClawbackIssuesEachOther(all); - testNotHoldingLptoken(all); - testAssetFrozen(all); - testSingleDepositAndClawback(all); + for (auto const& features : {all - fixAMMClawbackRounding, all}) + { + testAMMClawbackSpecificAmount(features); + testAMMClawbackExceedBalance(features); + testAMMClawbackAll(features); + testAMMClawbackSameIssuerAssets(features); + testAMMClawbackSameCurrency(features); + testAMMClawbackIssuesEachOther(features); + testNotHoldingLptoken(features); + testAssetFrozen(features); + testSingleDepositAndClawback(features); + testLastHolderLPTokenBalance(features); + } } }; BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple); diff --git a/src/xrpld/app/misc/AMMUtils.h b/src/xrpld/app/misc/AMMUtils.h index 52fe819a2..a3d6ba39e 100644 --- a/src/xrpld/app/misc/AMMUtils.h +++ b/src/xrpld/app/misc/AMMUtils.h @@ -123,6 +123,17 @@ isOnlyLiquidityProvider( Issue const& ammIssue, AccountID const& lpAccount); +/** Due to rounding, the LPTokenBalance of the last LP might + * not match the LP's trustline balance. If it's within the tolerance, + * update LPTokenBalance to match the LP's trustline balance. + */ +Expected +verifyAndAdjustLPTokenBalance( + Sandbox& sb, + STAmount const& lpTokens, + std::shared_ptr& ammSle, + AccountID const& account); + } // namespace ripple #endif // RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED diff --git a/src/xrpld/app/misc/detail/AMMUtils.cpp b/src/xrpld/app/misc/detail/AMMUtils.cpp index 10a918098..8a6917b8c 100644 --- a/src/xrpld/app/misc/detail/AMMUtils.cpp +++ b/src/xrpld/app/misc/detail/AMMUtils.cpp @@ -16,6 +16,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== + +#include #include #include #include @@ -462,4 +464,32 @@ isOnlyLiquidityProvider( return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE } +Expected +verifyAndAdjustLPTokenBalance( + Sandbox& sb, + STAmount const& lpTokens, + std::shared_ptr& ammSle, + AccountID const& account) +{ + if (auto const res = isOnlyLiquidityProvider(sb, lpTokens.issue(), account); + !res) + return Unexpected(res.error()); + else if (res.value()) + { + if (withinRelativeDistance( + lpTokens, + ammSle->getFieldAmount(sfLPTokenBalance), + Number{1, -3})) + { + ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); + sb.update(ammSle); + } + else + { + return Unexpected(tecAMM_INVALID_TOKENS); + } + } + return true; +} + } // namespace ripple diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index 162224ff9..43b61b855 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -151,6 +151,20 @@ AMMClawback::applyGuts(Sandbox& sb) if (!accountSle) return tecINTERNAL; // LCOV_EXCL_LINE + if (sb.rules().enabled(fixAMMClawbackRounding)) + { + // retrieve LP token balance inside the amendment gate to avoid + // inconsistent error behavior + auto const lpTokenBalance = ammLPHolds(sb, *ammSle, holder, j_); + if (lpTokenBalance == beast::zero) + return tecAMM_BALANCE; + + if (auto const res = verifyAndAdjustLPTokenBalance( + sb, lpTokenBalance, ammSle, holder); + !res) + return res.error(); // LCOV_EXCL_LINE + } + auto const expected = ammHolds( sb, *ammSle, @@ -248,10 +262,11 @@ AMMClawback::equalWithdrawMatchingOneAmount( STAmount const& amount) { auto frac = Number{amount} / amountBalance; - auto const amount2Withdraw = amount2Balance * frac; + auto amount2Withdraw = amount2Balance * frac; auto const lpTokensWithdraw = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac); + if (lpTokensWithdraw > holdLPtokens) // if lptoken balance less than what the issuer intended to clawback, // clawback all the tokens. Because we are doing a two-asset withdrawal, @@ -272,6 +287,42 @@ AMMClawback::equalWithdrawMatchingOneAmount( mPriorBalance, ctx_.journal); + auto const& rules = sb.rules(); + if (rules.enabled(fixAMMClawbackRounding)) + { + auto tokensAdj = + getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No); + + // LCOV_EXCL_START + if (tokensAdj == beast::zero) + return { + tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt}; + // LCOV_EXCL_STOP + + frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac); + auto amount2Rounded = + getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No); + + auto amountRounded = + getRoundedAsset(rules, amountBalance, frac, IsDeposit::No); + + return AMMWithdraw::withdraw( + sb, + ammSle, + ammAccount, + holder, + amountBalance, + amountRounded, + amount2Rounded, + lptAMMBalance, + tokensAdj, + 0, + FreezeHandling::fhIGNORE_FREEZE, + WithdrawAll::No, + mPriorBalance, + ctx_.journal); + } + // Because we are doing a two-asset withdrawal, // tfee is actually not used, so pass tfee as 0. return AMMWithdraw::withdraw( diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index e8569b48c..c6a464f21 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -313,24 +313,9 @@ AMMWithdraw::applyGuts(Sandbox& sb) // might not match the LP's trustline balance if (auto const res = - isOnlyLiquidityProvider(sb, lpTokens.issue(), account_); + verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_); !res) return {res.error(), false}; - else if (res.value()) - { - if (withinRelativeDistance( - lpTokens, - ammSle->getFieldAmount(sfLPTokenBalance), - Number{1, -3})) - { - ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); - sb.update(ammSle); - } - else - { - return {tecAMM_INVALID_TOKENS, false}; - } - } auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_); From d3d24f781bbfa35d993f2591cd523f7349b3d327 Mon Sep 17 00:00:00 2001 From: tequ Date: Thu, 19 Feb 2026 13:51:18 +0900 Subject: [PATCH 04/18] Merge fixAMMClawbackRounding amendment into featureAMMClawback amendment --- include/xrpl/protocol/detail/features.macro | 1 - src/test/app/AMMClawback_test.cpp | 339 ++++++-------------- src/xrpld/app/tx/detail/AMMClawback.cpp | 70 ++-- 3 files changed, 113 insertions(+), 297 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 738a8287b..3f48ac714 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -31,7 +31,6 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. -XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 4fec924f9..d2ad29003 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -629,21 +629,14 @@ class AMMClawback_test : public beast::unit_test::suite // Another 1 USD / 1.25 EUR was withdrawn. env.require(balance(alice, USD(2000))); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectBalances( - USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12})); - else - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2499000000000001), -12}, - STAmount{EUR, UINT64_C(3123750000000001), -12}, - IOUAmount{2793966937885988, -12})); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2499000000000001), -12}, + STAmount{EUR, UINT64_C(3123750000000001), -12}, + IOUAmount{2793966937885988, -12})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25)); - else - BEAST_EXPECT( - env.balance(alice, EUR) == - STAmount(EUR, UINT64_C(2876'249999999999), -12)); + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(2876'249999999999), -12)); // gw clawback 4000 USD, exceeding the current balance. We // will clawback all. @@ -744,29 +737,17 @@ class AMMClawback_test : public beast::unit_test::suite env.require(balance(bob, USD(4000))); // Alice gets 1000 XRP back. - if (features[fixAMMClawbackRounding]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(1000) - XRPAmount(1))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(1000))); + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000) - XRPAmount(1))); aliceXrpBalance = env.balance(alice, XRP); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectBalances( - USD(2500), XRP(5000), IOUAmount{3535533905932737, -9})); - else - BEAST_EXPECT(amm.expectBalances( - USD(2500), - XRPAmount(5000000001), - IOUAmount{3'535'533'905932738, -9})); + BEAST_EXPECT(amm.expectBalances( + USD(2500), + XRPAmount(5000000001), + IOUAmount{3'535'533'905932738, -9})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{7071067811865474, -10})); - else - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9})); @@ -783,28 +764,16 @@ class AMMClawback_test : public beast::unit_test::suite expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20))); bobXrpBalance = env.balance(bob, XRP); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectBalances( - USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9})); - else - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(2490000000000001), -12}, - XRPAmount(4980000001), - IOUAmount{3521391'770309008, -9})); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2490000000000001), -12}, + XRPAmount(4980000001), + IOUAmount{3521391'770309008, -9})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectLPTokens( - alice, IOUAmount{7071067811865474, -10})); - else - BEAST_EXPECT( - amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); - else - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); // gw2 clawback 200 EUR from amm2. env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)), @@ -814,22 +783,14 @@ class AMMClawback_test : public beast::unit_test::suite env.require(balance(alice, EUR(4000))); env.require(balance(bob, EUR(3000))); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(600))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(600) - XRPAmount{1})); + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(600) - XRPAmount{1})); aliceXrpBalance = env.balance(alice, XRP); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm2.expectBalances( - EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(2800), - XRPAmount(8400000001), - IOUAmount{4849742261192856, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), + XRPAmount(8400000001), + IOUAmount{4849742261192856, -9})); BEAST_EXPECT( amm2.expectLPTokens(alice, IOUAmount{1385640646055102, -9})); @@ -847,32 +808,18 @@ class AMMClawback_test : public beast::unit_test::suite env.require(balance(bob, USD(4000))); // Alice gets 1000 XRP back. - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(1000) - XRPAmount{1})); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(1000))); + BEAST_EXPECT( + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); aliceXrpBalance = env.balance(alice, XRP); BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); - else - BEAST_EXPECT( - amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectBalances( - USD(1'990), - XRPAmount{3'980'000'001}, - IOUAmount{2814284989122459, -9})); - else - BEAST_EXPECT(amm.expectBalances( - STAmount{USD, UINT64_C(1990000000000001), -12}, - XRPAmount{3'980'000'001}, - IOUAmount{2814284989122460, -9})); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(1990000000000001), -12}, + XRPAmount{3'980'000'001}, + IOUAmount{2814284989122460, -9})); // gw clawback 1000 USD from bob in amm, which also exceeds bob's // balance in amm. All bob's lptoken in amm will be consumed, which @@ -915,14 +862,10 @@ class AMMClawback_test : public beast::unit_test::suite // Alice now does not have any lptoken in amm2 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(2000), - XRPAmount(6000000001), - IOUAmount{3464101615137754, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), + XRPAmount(6000000001), + IOUAmount{3464101615137754, -9})); // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's // balance. All bob's lptokens will be consumed, which corresponds @@ -946,14 +889,10 @@ class AMMClawback_test : public beast::unit_test::suite BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0))); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); - else - BEAST_EXPECT(amm2.expectBalances( - EUR(1000), - XRPAmount(3000000001), - IOUAmount{1732050807568877, -9})); + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), + XRPAmount(3000000001), + IOUAmount{1732050807568877, -9})); } } @@ -1430,20 +1369,11 @@ class AMMClawback_test : public beast::unit_test::suite // gw claws back 1000 USD from gw2. env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectBalances( - USD(5000), EUR(10000), IOUAmount{7071067811865475, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(5000), EUR(10000), IOUAmount{7071067811865474, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(5000), EUR(10000), IOUAmount{7071067811865474, -12})); BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT( - amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); - else - BEAST_EXPECT( - amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1455,28 +1385,14 @@ class AMMClawback_test : public beast::unit_test::suite // gw2 claws back 1000 EUR from gw. env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectBalances( - USD(4500), EUR(9000), IOUAmount{6363961030678928, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(4500), - STAmount(EUR, UINT64_C(9000000000000001), -12), - IOUAmount{6363961030678927, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(4500), + STAmount(EUR, UINT64_C(9000000000000001), -12), + IOUAmount{6363961030678927, -12})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); - else - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT( - amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); - else - BEAST_EXPECT( - amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1489,28 +1405,14 @@ class AMMClawback_test : public beast::unit_test::suite // gw2 claws back 4000 EUR from alice. env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT(amm.expectBalances( - USD(2500), EUR(5000), IOUAmount{3535533905932738, -12})); - else - BEAST_EXPECT(amm.expectBalances( - USD(2500), - STAmount(EUR, UINT64_C(5000000000000001), -12), - IOUAmount{3535533905932737, -12})); + BEAST_EXPECT(amm.expectBalances( + USD(2500), + STAmount(EUR, UINT64_C(5000000000000001), -12), + IOUAmount{3535533905932737, -12})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); - else - BEAST_EXPECT( - amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); - if (!features[fixAMMClawbackRounding]) - BEAST_EXPECT( - amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); - else - BEAST_EXPECT( - amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12})); @@ -1973,22 +1875,13 @@ class AMMClawback_test : public beast::unit_test::suite isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); BEAST_EXPECT(res && res.value()); - if (!features[fixAMMClawbackRounding]) - { - env(amm::ammClawback(gw, alice, USD, XRP, USD(1)), - ter(tecAMM_BALANCE)); - BEAST_EXPECT(amm.ammExists()); - } - else - { - auto const lpBalance = IOUAmount{989, -12}; - env(amm::ammClawback(gw, alice, USD, XRP, USD(1))); - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(7000000000000000), -28), - XRPAmount(1), - lpBalance)); - BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); - } + auto const lpBalance = IOUAmount{989, -12}; + env(amm::ammClawback(gw, alice, USD, XRP, USD(1))); + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(7000000000000000), -28), + XRPAmount(1), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); } // IOU/XRP pool. AMMClawback part of last holder's USD balance @@ -2020,22 +1913,12 @@ class AMMClawback_test : public beast::unit_test::suite env(amm::ammClawback(gw, alice, USD, XRP, USD(0.5))); - if (!features[fixAMMClawbackRounding]) - { - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(5013266196407), -13), - XRPAmount(1002654), - IOUAmount{708'9829046744941, -13})); - } - else - { - auto const lpBalance = IOUAmount{708'9829046743238, -13}; - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(5013266196406999), -16), - XRPAmount(1002655), - lpBalance)); - BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); - } + auto const lpBalance = IOUAmount{708'9829046743238, -13}; + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5013266196406999), -16), + XRPAmount(1002655), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); } // IOU/XRP pool. AMMClawback all of last holder's USD balance @@ -2066,19 +1949,8 @@ class AMMClawback_test : public beast::unit_test::suite isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); BEAST_EXPECT(res && res.value()); - if (!features[fixAMMClawbackRounding]) - { - env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt)); - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(2410000000000000), -28), - XRPAmount(1), - IOUAmount{34, -11})); - } - else - { - env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt)); - BEAST_EXPECT(!amm.ammExists()); - } + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); } // IOU/IOU pool, different issuers @@ -2119,17 +1991,8 @@ class AMMClawback_test : public beast::unit_test::suite isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); BEAST_EXPECT(res && res.value()); - if (features[fixAMMClawbackRounding]) - { - env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); - BEAST_EXPECT(!amm.ammExists()); - } - else - { - env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), - ter(tecINTERNAL)); - BEAST_EXPECT(amm.ammExists()); - } + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); } // IOU/IOU pool, same issuer @@ -2167,19 +2030,9 @@ class AMMClawback_test : public beast::unit_test::suite isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); BEAST_EXPECT(res && res.value()); - if (features[fixAMMClawbackRounding]) - { - env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), - txflags(tfClawTwoAssets)); - BEAST_EXPECT(!amm.ammExists()); - } - else - { - env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), - txflags(tfClawTwoAssets), - ter(tecINTERNAL)); - BEAST_EXPECT(amm.ammExists()); - } + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + txflags(tfClawTwoAssets)); + BEAST_EXPECT(!amm.ammExists()); } // IOU/IOU pool, larger asset ratio @@ -2218,37 +2071,25 @@ class AMMClawback_test : public beast::unit_test::suite isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); BEAST_EXPECT(res && res.value()); - if (!features[fixAMMClawbackRounding]) - { - // sqrt(amount * amount2) >= LPTokens and exceeds the allowed - // tolerance - env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), - ter(tecINVARIANT_FAILED)); - BEAST_EXPECT(amm.ammExists()); - } - else - { - env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), - txflags(tfClawTwoAssets)); - auto const lpBalance = IOUAmount{5, -12}; - BEAST_EXPECT(amm.expectBalances( - STAmount(USD, UINT64_C(4), -15), - STAmount(EUR, UINT64_C(8), -9), - lpBalance)); - BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); - } + env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), + txflags(tfClawTwoAssets)); + auto const lpBalance = IOUAmount{5, -12}; + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(4), -15), + STAmount(EUR, UINT64_C(8), -9), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); } } void run() override { - FeatureBitset const all{ - jtx::supported_amendments() | fixAMMClawbackRounding}; + FeatureBitset const all{jtx::supported_amendments()}; testInvalidRequest(); testFeatureDisabled(all - featureAMMClawback); - for (auto const& features : {all - fixAMMClawbackRounding, all}) + for (auto const& features : {all}) { testAMMClawbackSpecificAmount(features); testAMMClawbackExceedBalance(features); diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index 43b61b855..4287067cf 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -151,19 +151,16 @@ AMMClawback::applyGuts(Sandbox& sb) if (!accountSle) return tecINTERNAL; // LCOV_EXCL_LINE - if (sb.rules().enabled(fixAMMClawbackRounding)) - { - // retrieve LP token balance inside the amendment gate to avoid - // inconsistent error behavior - auto const lpTokenBalance = ammLPHolds(sb, *ammSle, holder, j_); - if (lpTokenBalance == beast::zero) - return tecAMM_BALANCE; + // retrieve LP token balance inside the amendment gate to avoid + // inconsistent error behavior + auto const lpTokenBalance = ammLPHolds(sb, *ammSle, holder, j_); + if (lpTokenBalance == beast::zero) + return tecAMM_BALANCE; - if (auto const res = verifyAndAdjustLPTokenBalance( - sb, lpTokenBalance, ammSle, holder); - !res) - return res.error(); // LCOV_EXCL_LINE - } + if (auto const res = + verifyAndAdjustLPTokenBalance(sb, lpTokenBalance, ammSle, holder); + !res) + return res.error(); // LCOV_EXCL_LINE auto const expected = ammHolds( sb, @@ -288,53 +285,32 @@ AMMClawback::equalWithdrawMatchingOneAmount( ctx_.journal); auto const& rules = sb.rules(); - if (rules.enabled(fixAMMClawbackRounding)) - { - auto tokensAdj = - getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No); - // LCOV_EXCL_START - if (tokensAdj == beast::zero) - return { - tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt}; - // LCOV_EXCL_STOP + auto tokensAdj = + getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No); - frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac); - auto amount2Rounded = - getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No); + // LCOV_EXCL_START + if (tokensAdj == beast::zero) + return {tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt}; + // LCOV_EXCL_STOP - auto amountRounded = - getRoundedAsset(rules, amountBalance, frac, IsDeposit::No); + frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac); + auto amount2Rounded = + getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No); - return AMMWithdraw::withdraw( - sb, - ammSle, - ammAccount, - holder, - amountBalance, - amountRounded, - amount2Rounded, - lptAMMBalance, - tokensAdj, - 0, - FreezeHandling::fhIGNORE_FREEZE, - WithdrawAll::No, - mPriorBalance, - ctx_.journal); - } + auto amountRounded = + getRoundedAsset(rules, amountBalance, frac, IsDeposit::No); - // Because we are doing a two-asset withdrawal, - // tfee is actually not used, so pass tfee as 0. return AMMWithdraw::withdraw( sb, ammSle, ammAccount, holder, amountBalance, - amount, - toSTAmount(amount2Balance.issue(), amount2Withdraw), + amountRounded, + amount2Rounded, lptAMMBalance, - toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac), + tokensAdj, 0, FreezeHandling::fhIGNORE_FREEZE, WithdrawAll::No, From 58e278289bbf9be624a883a7b4309c0361e3f7c0 Mon Sep 17 00:00:00 2001 From: tequ Date: Tue, 20 Jan 2026 12:12:45 +0900 Subject: [PATCH 05/18] Add tests for Hooks fee --- src/test/app/SetHook_test.cpp | 567 +++++++++++++++++++++++----------- 1 file changed, 394 insertions(+), 173 deletions(-) diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 4c2fc39d3..16c06d7be 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -75,6 +75,16 @@ using JSSMap = [[maybe_unused]] std::string const x##_hash_str = to_string(x##_hash); \ [[maybe_unused]] Keylet const x##_keylet = keylet::hookDefinition(x##_hash); +#define EXPECT_HOOK_FEE(x, fee) \ + do \ + { \ + auto const hookSLE = env.le(x##_keylet); \ + BEAST_EXPECTS( \ + hookSLE->getFieldAmount(sfFee) == XRPAmount{fee}, \ + "Hook fee mismatch: expected " #fee " got " + \ + to_string(hookSLE->getFieldAmount(sfFee))); \ + } while (false) + class SetHook0_test : public beast::unit_test::suite { private: @@ -2710,6 +2720,7 @@ public: M("Install Accept Hook"), HSFEE); env.close(); + EXPECT_HOOK_FEE(accept, 9); env(pay(bob, alice, XRP(1)), M("Test Accept Hook"), fee(XRP(1))); env.close(); @@ -2731,6 +2742,7 @@ public: M("Install Rollback Hook"), HSFEE); env.close(); + EXPECT_HOOK_FEE(rollback, 9); env(pay(bob, alice, XRP(1)), M("Test Rollback Hook"), @@ -2793,7 +2805,7 @@ public: // same loop again but with a guard call { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i64))) (type (;1;) (func (param i32 i32) (result i32))) @@ -2828,15 +2840,17 @@ public: (export "hook" (func 2))) )[test.hook]"]; - env(ripple::test::jtx::hook(alice, {{hso(hook)}}, 0), + HASH_WASM(hook); + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm)}}, 0), M("Loop 1 with guards"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 14); } // simple looping, c { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -2852,11 +2866,14 @@ public: return accept(0,0,2); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("Loop 2 in C"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 100); env(pay(bob, alice, XRP(1)), M("Test Loop 2"), fee(XRP(1))); env.close(); @@ -2864,7 +2881,7 @@ public: // complex looping, c { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -2896,11 +2913,14 @@ public: return accept(0,0,2); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("Loop 3 in C"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 1944); env(pay(bob, alice, XRP(1)), M("Test Loop 3"), fee(XRP(1))); env.close(); @@ -2908,7 +2928,7 @@ public: // complex looping missing a guard { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -2942,7 +2962,8 @@ public: } )[test.hook]"]; - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("Loop 4 in C"), HSFEE, ter(temMALFORMED)); @@ -2963,7 +2984,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -3284,10 +3305,12 @@ public: } )[test.hook]"]; - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + HASH_WASM(hook); + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set emit"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 342); Json::Value invoke; invoke[jss::TransactionType] = "Invoke"; @@ -3858,7 +3881,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -3931,12 +3954,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set etxn_details"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 88); // invoke the hook env(pay(bob, alice, XRP(1)), M("test etxn_details"), fee(XRP(1))); @@ -3952,7 +3977,7 @@ public: auto const alice = Account{"alice"}; auto const bob = Account{"bob"}; - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4013,8 +4038,9 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); + HASH_WASM(hook); // install the hook on alice - auto hsobj = hso(hook, overrideFlag); + auto hsobj = hso(hook_wasm, overrideFlag); hsobj[jss::HookOn] = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFF" "FE"; // payment high @@ -4022,6 +4048,7 @@ public: M("set etxn_fee_base"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 77); // invoke the hook env(pay(bob, alice, XRP(1)), M("test etxn_fee_base"), fee(XRP(1))); @@ -4057,7 +4084,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4097,12 +4124,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set etxn_nonce"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 11644); // invoke the hook env(pay(bob, alice, XRP(1)), M("test etxn_nonce"), fee(XRP(1))); @@ -4122,7 +4151,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4147,12 +4176,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set etxn_reserve"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 69); // invoke the hook env(pay(bob, alice, XRP(1)), M("test etxn_reserve"), fee(XRP(1))); @@ -4171,7 +4202,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4188,12 +4219,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set fee_base"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 20); // invoke the hook env(pay(bob, alice, XRP(1)), M("test fee_base"), fee(XRP(1))); @@ -4212,7 +4245,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4321,11 +4354,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_compare"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 583); env(pay(bob, alice, XRP(1)), M("test float_compare"), fee(XRP(1))); env.close(); @@ -4345,7 +4381,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4520,11 +4556,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_divide"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 1799); env(pay(bob, alice, XRP(1)), M("test float_divide"), fee(XRP(1))); env.close(); @@ -4544,7 +4583,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4649,11 +4688,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_int"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 1178); env(pay(bob, alice, XRP(1)), M("test float_int"), fee(XRP(1))); env.close(); @@ -4673,7 +4715,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4737,11 +4779,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_invert"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 329); env(pay(bob, alice, XRP(1)), M("test float_invert"), fee(XRP(1))); env.close(); @@ -4761,7 +4806,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4820,11 +4865,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_log"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 388); env(pay(bob, alice, XRP(1)), M("test float_log"), fee(XRP(1))); env.close(); @@ -4844,7 +4892,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -4949,11 +4997,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_mantissa"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 309); env(pay(bob, alice, XRP(1)), M("test float_mantissa"), fee(XRP(1))); env.close(); @@ -4973,7 +5024,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -5104,11 +5155,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_mulratio"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 1683); env(pay(bob, alice, XRP(1)), M("test float_mulratio"), fee(XRP(1))); env.close(); @@ -5128,7 +5182,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -5403,12 +5457,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_multiply"), HSFEE); env.close(); - + EXPECT_HOOK_FEE(hook, 3180); env(pay(bob, alice, XRP(1)), M("test float_multiply"), fee(XRP(1))); env.close(); } @@ -5427,7 +5483,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -5474,11 +5530,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_negate"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 105); env(pay(bob, alice, XRP(1)), M("test float_negate"), fee(XRP(1))); env.close(); @@ -5498,7 +5557,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -5515,11 +5574,14 @@ public: : rollback(0,0,1); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_one"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 20); env(pay(bob, alice, XRP(1)), M("test float_one"), fee(XRP(1))); env.close(); @@ -5539,7 +5601,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -5593,11 +5655,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_root"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 257); env(pay(bob, alice, XRP(1)), M("test float_root"), fee(XRP(1))); env.close(); @@ -5617,7 +5682,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -5666,11 +5731,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_set"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 343); env(pay(bob, alice, XRP(1)), M("test float_set"), fee(XRP(1))); env.close(); @@ -5690,7 +5758,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -5779,11 +5847,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_sign"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 296); env(pay(bob, alice, XRP(1)), M("test float_sign"), fee(XRP(1))); env.close(); @@ -5803,7 +5874,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -5984,11 +6055,14 @@ public: } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_sto"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 920); env(pay(bob, alice, XRP(1)), M("test float_sto"), fee(XRP(1))); env.close(); @@ -6008,7 +6082,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -6126,11 +6200,14 @@ public: } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_sto_set"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 187); env(pay(bob, alice, XRP(1)), M("test float_sto_set"), fee(XRP(1))); env.close(); @@ -6150,7 +6227,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -6306,11 +6383,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set float_sum"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 1735); env(pay(bob, alice, XRP(1)), M("test float_sum"), fee(XRP(1))); env.close(); @@ -6330,7 +6410,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -6357,12 +6437,15 @@ public: accept((uint32_t)acc, 20, 0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set hook_account"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 72); // invoke the hook env(pay(bob, alice, XRP(1)), M("test hook_account"), fee(XRP(1))); @@ -6390,7 +6473,8 @@ public: } // install the same hook bob - env(ripple::test::jtx::hook(bob, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + bob, {{hso(hook_wasm, overrideFlag)}}, 0), M("set hook_account 2"), HSFEE); env.close(); @@ -6452,7 +6536,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -6481,12 +6565,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set hook_again"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 54); env(pay(bob, alice, XRP(1)), M("test hook_again"), fee(XRP(1))); env.close(); @@ -6529,7 +6615,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -6555,12 +6641,15 @@ public: accept((uint32_t)hash, 32, 0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set hook_hash"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 62); // invoke the hook env(pay(bob, alice, XRP(1)), M("test hook_hash"), fee(XRP(1))); @@ -6588,7 +6677,7 @@ public: BEAST_EXPECT(memcmp(hash.data(), retStr.data(), 32) == 0); } - TestHook hook2 = wasm[R"[test.hook]( + TestHook hook2_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -6614,12 +6703,15 @@ public: accept((uint32_t)hash, 32, 0); } )[test.hook]"]; + HASH_WASM(hook2); // install a slightly different hook on bob - env(ripple::test::jtx::hook(bob, {{hso(hook2, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + bob, {{hso(hook2_wasm, overrideFlag)}}, 0), M("set hook_hash 2"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook2, 62); // invoke the hook env(pay(bob, alice, XRP(1)), M("test hook_hash 2"), fee(XRP(1))); @@ -6662,11 +6754,8 @@ public: BEAST_EXPECT(memcmp(hash1.data(), hash2.data(), 32) != 0); // compute the hashes - auto computedHash2 = ripple::sha512Half_s( - ripple::Slice(hook.data(), hook.size())); - - auto computedHash1 = ripple::sha512Half_s( - ripple::Slice(hook2.data(), hook2.size())); + auto computedHash2 = hook_hash; + auto computedHash1 = hook2_hash; // ensure the computed hashes match BEAST_EXPECT(computedHash1 == hash1); @@ -6689,7 +6778,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -6771,6 +6860,7 @@ public: } )[test.hook]"]; + HASH_WASM(hook); Json::Value jv; jv[jss::Account] = alice.human(); @@ -6779,7 +6869,7 @@ public: jv[jss::Hooks] = Json::Value{Json::arrayValue}; Json::Value iv; - iv[jss::CreateCode] = strHex(hook); + iv[jss::CreateCode] = strHex(hook_wasm); iv[jss::HookOn] = "0000000000000000000000000000000000000000000000000000000000000000"; iv[jss::HookApiVersion] = 0U; @@ -6798,6 +6888,7 @@ public: jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("set hook_param"), HSFEE, ter(tesSUCCESS)); env.close(); + EXPECT_HOOK_FEE(hook, 2412); // invoke env(pay(bob, alice, XRP(1)), M("test hook_param"), fee(XRP(1))); @@ -6958,6 +7049,7 @@ public: )[test.hook]"]; HASH_WASM(checker); + HASH_WASM(setter); Json::Value jv; jv[jss::Account] = alice.human(); @@ -7007,6 +7099,8 @@ public: env(jv, M("set hook_param_set"), HSFEE, ter(tesSUCCESS)); env.close(); + EXPECT_HOOK_FEE(checker, 475); + EXPECT_HOOK_FEE(setter, 759); // invoke env(pay(bob, alice, XRP(1)), M("test hook_param_set"), fee(XRP(1))); @@ -7025,7 +7119,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -7037,14 +7131,21 @@ public: accept(0,0,hook_pos()); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice in all four spots env(ripple::test::jtx::hook( - alice, {{hso(hook), hso(hook), hso(hook), hso(hook)}}, 0), + alice, + {{hso(hook_wasm), + hso(hook_wasm), + hso(hook_wasm), + hso(hook_wasm)}}, + 0), M("set hook_pos"), HSFEE, ter(tesSUCCESS)); env.close(); + EXPECT_HOOK_FEE(hook, 11); // invoke the hooks env(pay(bob, alice, XRP(1)), M("test hook_pos"), fee(XRP(1))); @@ -7151,6 +7252,7 @@ public: } )[test.hook]"]; + HASH_WASM(skip); HASH_WASM(pos); // install the hook on alice in one places @@ -7165,6 +7267,8 @@ public: HSFEE, ter(tesSUCCESS)); env.close(); + EXPECT_HOOK_FEE(skip, 263); + EXPECT_HOOK_FEE(pos, 11); // invoke the hooks { @@ -7201,7 +7305,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -7279,12 +7383,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set ledger_keylet"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 415); env(pay(bob, alice, XRP(1)), M("test ledger_keylet"), fee(XRP(1))); env.close(); @@ -7303,7 +7409,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -7328,12 +7434,14 @@ public: accept((uint32_t)hash, 32, 0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set ledger_last_hash"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 59); for (uint32_t i = 0; i < 3; ++i) { @@ -7377,7 +7485,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -7389,12 +7497,14 @@ public: accept(0,0,ledger_last_time()); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set ledger_last_time"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 11); // invoke the hook a few times for (uint32_t i = 0; i < 3; ++i) @@ -7445,7 +7555,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -7472,12 +7582,14 @@ public: accept((uint32_t)nonce, 64, 0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set ledger_nonce"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 74); // invoke the hook auto const seq = @@ -7543,7 +7655,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -7555,12 +7667,14 @@ public: accept(0,0,ledger_seq()); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set ledger_seq"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 11); // invoke the hook a few times for (uint32_t i = 0; i < 3; ++i) @@ -7602,7 +7716,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -7652,12 +7766,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set meta_slot"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 139); env(pay(bob, alice, XRP(1)), M("test meta_slot"), fee(XRP(1))); env.close(); @@ -7699,7 +7815,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -7765,6 +7881,7 @@ public: return accept(0,0,2); } )[test.hook]"]; + HASH_WASM(hook); // before featureHooksUpdate1 env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), @@ -7777,10 +7894,11 @@ public: env.close(); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set xpop_slot"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 3245); auto checkResult = [this](auto const& meta, uint64_t expectedCode) -> void { @@ -7820,7 +7938,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -7867,12 +7985,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set otxn_field"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 732); // invoke the hook env(pay(alice, bob, XRP(1)), M("test otxn_field"), fee(XRP(1))); @@ -7890,7 +8010,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -7950,12 +8070,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set otxn_id"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 1077); // invoke the hook env(pay(bob, alice, XRP(1)), M("test otxn_id"), fee(XRP(1))); @@ -7973,7 +8095,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8032,12 +8154,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set otxn_slot"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 6442); // invoke the hook env(pay(bob, alice, XRP(1)), M("test otxn_slot"), fee(XRP(1))); @@ -8055,7 +8179,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8092,12 +8216,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set otxn_type"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 51); // invoke the hook env(pay(bob, alice, XRP(1)), M("test otxn_type"), fee(XRP(1))); @@ -8126,7 +8252,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -8208,12 +8334,14 @@ public: } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set otxn_param"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 2412); // invoke Json::Value invoke; @@ -8249,7 +8377,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8339,12 +8467,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 232); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot"), fee(XRP(1))); @@ -8362,7 +8492,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8397,12 +8527,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_clear"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 83); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_clear"), fee(XRP(1))); @@ -8420,7 +8552,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8462,12 +8594,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_count"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 97); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_count"), fee(XRP(1))); @@ -8485,7 +8619,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8537,12 +8671,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_float"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 112); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_float"), fee(XRP(1))); @@ -8560,7 +8696,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8647,12 +8783,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_set"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 11653); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_set"), fee(XRP(1))); @@ -8670,7 +8808,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8726,12 +8864,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_size"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 114); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_size"), fee(XRP(1))); @@ -8750,7 +8890,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8847,12 +8987,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_subarray"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 6212); // generate an array of memos to attach Json::Value jv; @@ -8890,7 +9032,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -8967,12 +9109,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_subfield"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 6109); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_subfield"), fee(XRP(1))); @@ -8993,7 +9137,7 @@ public: // set up a trustline which we can retrieve later env(trust(alice, bob["USD"](600))); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -9106,12 +9250,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set slot_subfield"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 284); // invoke the hook env(pay(bob, alice, XRP(1)), M("test slot_type"), fee(XRP(1))); @@ -9131,7 +9277,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -9190,12 +9336,15 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set state"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 2254); // invoke the hook env(pay(bob, alice, XRP(1)), M("test state"), fee(XRP(1))); @@ -9205,7 +9354,7 @@ public: // override hook with a second version that just reads those state // objects { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -9246,12 +9395,15 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set state 2"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 2134); // invoke the hook env(pay(bob, alice, XRP(1)), M("test state 2"), fee(XRP(1))); @@ -9272,7 +9424,7 @@ public: env.fund(XRP(10000), bob); { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -9312,12 +9464,15 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set state_foreign"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 72); // invoke the hook env(pay(bob, alice, XRP(1)), M("test state_foreign"), fee(XRP(1))); @@ -9325,7 +9480,7 @@ public: // set a second hook on bob that will read the state objects from alice { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -9402,12 +9557,15 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on bob - env(ripple::test::jtx::hook(bob, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + bob, {{hso(hook_wasm, overrideFlag)}}, 0), M("set state_foreign 2"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 2408); // invoke the hook @@ -9423,7 +9581,7 @@ public: testcase("Test state_foreign_set max"); using namespace jtx; - static const std::vector ns_maxHook = { + static const std::vector ns_maxHook_wasm = { 0x00U, 0x61U, 0x73U, 0x6dU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x36U, 0x07U, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7fU, 0x60U, 0x02U, 0x7fU, 0x7fU, 0x01U, 0x7eU, 0x60U, 0x03U, 0x7fU, 0x7fU, @@ -9483,6 +9641,7 @@ public: 0x52U, 0x65U, 0x61U, 0x63U, 0x68U, 0x65U, 0x64U, 0x00U, 0x6bU, 0x65U, 0x79U, 0x32U, 0x00U, 0x63U, 0x6fU, 0x6eU, 0x74U, 0x65U, 0x6eU, 0x74U, 0x32U}; + HASH_WASM(ns_maxHook); Env env{*this, features}; @@ -9493,10 +9652,11 @@ public: // install the hook on alice env(ripple::test::jtx::hook( - alice, {{hso(ns_maxHook, overrideFlag)}}, 0), + alice, {{hso(ns_maxHook_wasm, overrideFlag)}}, 0), M("set state_foreign_set_max"), HSFEE); env.close(); + EXPECT_HOOK_FEE(ns_maxHook, 103); // invoke the hook for (uint32_t i = 0; i < 255; ++i) @@ -9674,6 +9834,7 @@ public: env(json, M("set state_foreign_set"), HSFEE); env.close(); + EXPECT_HOOK_FEE(grantor, 103); } // install the grantee hook on bob @@ -9683,6 +9844,7 @@ public: bob, {{hso(grantee_wasm, overrideFlag)}}, 0); env(json, M("set state_foreign_set 2"), HSFEE); env.close(); + EXPECT_HOOK_FEE(grantee, 234); } auto const aliceid = Account("alice").id(); @@ -9944,6 +10106,7 @@ public: bob, {{hso(exhaustion_wasm, overrideFlag)}}, 0); env(json, M("set state_foreign_set 12"), HSFEE); env.close(); + EXPECT_HOOK_FEE(exhaustion, 10582); } // now invoke repeatedly until exhaustion is reached @@ -10044,7 +10207,7 @@ public: // bounds and buffer size checks { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -10087,12 +10250,15 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set state_set 1"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 143); BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1); @@ -10140,7 +10306,7 @@ public: // first hook will set two state objects with different keys and data on // alice { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -10211,12 +10377,15 @@ public: } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set state_set 1"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 85); BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1); @@ -10302,7 +10471,7 @@ public: // make amother hook to override an existing state and delete an // existing state { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -10357,8 +10526,9 @@ public: } )[test.hook]"]; + HASH_WASM(hook); - TestHook hook2 = wasm[R"[test.hook]( + TestHook hook2_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -10402,14 +10572,21 @@ public: } )[test.hook]"]; + HASH_WASM(hook2); + // install the hook on alice env(ripple::test::jtx::hook( alice, - {{{hso(hook, overrideFlag)}, {}, {}, {hso(hook2, 0)}}}, + {{{hso(hook_wasm, overrideFlag)}, + {}, + {}, + {hso(hook2_wasm, 0)}}}, 0), M("set state_set 2"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 82); + EXPECT_HOOK_FEE(hook2, 525); // two hooks + two state objects = 4 BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 4); @@ -10418,7 +10595,7 @@ public: // updated state is also available on his side. caution must be // taken because bob's hooks will execute first if bob's is the // otxn. therefore we will flip to a payment from alice to bob here - TestHook hook3 = wasm[R"[test.hook]( + TestHook hook3_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -10478,12 +10655,15 @@ public: } )[test.hook]"]; + HASH_WASM(hook3); // install the hook on bob - env(ripple::test::jtx::hook(bob, {{hso(hook3, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + bob, {{hso(hook3_wasm, overrideFlag)}}, 0), M("set state_set 3"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook3, 560); // invoke the hook with cho (rollback after alice's hooks have // executed) @@ -10536,7 +10716,7 @@ public: // create a hook state inside the weak side of an execution, while the // strong side is rolled back { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -10580,15 +10760,17 @@ public: } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice, deleting the other hook env(ripple::test::jtx::hook( alice, - {{{hso(hook, overrideFlag)}, {}, {}, {hso_delete()}}}, + {{{hso(hook_wasm, overrideFlag)}, {}, {}, {hso_delete()}}}, 0), M("set state_set 4"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 52); // invoke from alice to cho, this will cause a rollback, however the // hook state should still be updated because the hook specified @@ -10701,6 +10883,7 @@ public: env(json, M("set state_set 6"), HSFEE); env.close(); + EXPECT_HOOK_FEE(exhaustion, 54114); } // now invoke repeatedly until exhaustion is reached @@ -10853,6 +11036,7 @@ public: to_string(UINT256_BIT[ttACCOUNT_SET]); env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS)); env.close(); + EXPECT_HOOK_FEE(scaled_state, 227); BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 1); BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount)); @@ -10903,7 +11087,7 @@ public: // tests for set_state_cache if (extHookStateEnabled) { - TestHook extended_state_reserve_hook = wasm[R"[test.hook]( + TestHook extended_state_reserve_hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -10948,14 +11132,17 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(extended_state_reserve_hook); // install the hook on gary - Json::Value jv = hso(extended_state_reserve_hook, overrideFlag); + Json::Value jv = + hso(extended_state_reserve_hook_wasm, overrideFlag); jv[jss::HookOn] = "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff" "bfffff"; // only invoke high env(ripple::test::jtx::hook(hank, {{jv}}, 0), HSFEE); env.close(); + EXPECT_HOOK_FEE(extended_state_reserve_hook, 95); Json::Value jv1 = noop(hank); jv1[sfHookStateScale.fieldName] = 8; @@ -10998,7 +11185,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11168,19 +11355,22 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set sto_emplace"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 15024); // invoke the hook env(pay(bob, alice, XRP(1)), M("test sto_emplace"), fee(XRP(1))); } { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11217,6 +11407,7 @@ public: accept(0,0,result); } )[test.hook]"]; + HASH_WASM(hook); for (auto f : {features, features - fixHookAPI20251128}) { @@ -11229,10 +11420,11 @@ public: // install the hook on alice env(ripple::test::jtx::hook( - alice, {{hso(hook, overrideFlag)}}, 0), + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set sto_emplace"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 36); // invoke the hook env(pay(bob, alice, XRP(1)), @@ -11270,7 +11462,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11383,12 +11575,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set sto_erase"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 10021); // invoke the hook env(pay(bob, alice, XRP(1)), M("test sto_erase"), fee(XRP(1))); @@ -11408,7 +11602,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11458,19 +11652,22 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook( + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set sto_subarray"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 95); // invoke the hook env(pay(bob, alice, XRP(1)), M("test sto_subarray"), fee(XRP(1))); } { - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11501,6 +11698,7 @@ public: accept(0,0,result1+result2); } )[test.hook]"]; + HASH_WASM(hook); for (auto isfixHookAPI20251128 : {true, false}) { @@ -11513,10 +11711,11 @@ public: // install the hook on alice env(ripple::test::jtx::hook( - alice, {{hso(hook, overrideFlag)}}, 0), + alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set sto_subarray"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 19); // invoke the hook env(pay(bob, alice, XRP(1)), @@ -11570,7 +11769,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11630,12 +11829,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set sto_subfield"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 123); // invoke the hook env(pay(bob, alice, XRP(1)), M("test sto_subfield"), fee(XRP(1))); @@ -11654,7 +11855,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11713,12 +11914,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set sto_validate"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 130); // invoke the hook env(pay(bob, alice, XRP(1)), M("test sto_validate"), fee(XRP(1))); @@ -11917,7 +12120,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11943,12 +12146,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set trace"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 103); // invoke the hook env(pay(bob, alice, XRP(1)), M("test trace"), fee(XRP(1))); @@ -11967,7 +12172,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -11987,12 +12192,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set trace_float"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 37); // invoke the hook env(pay(bob, alice, XRP(1)), M("test trace_float"), fee(XRP(1))); @@ -12011,7 +12218,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -12031,12 +12238,14 @@ public: return accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set trace_num"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 37); // invoke the hook env(pay(bob, alice, XRP(1)), M("test trace_num"), fee(XRP(1))); @@ -12053,7 +12262,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -12299,12 +12508,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set util_accid"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 3101); // invoke the hook env(pay(bob, alice, XRP(1)), M("test util_accid"), fee(XRP(1))); @@ -12323,7 +12534,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -12980,12 +13191,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set util_keylet"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 1546); // invoke the hook env(pay(bob, alice, XRP(1)), M("test util_keylet"), fee(XRP(1))); @@ -13003,7 +13216,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -13433,12 +13646,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set util_raddr"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 4279); // invoke the hook env(pay(bob, alice, XRP(1)), M("test util_raddr"), fee(XRP(1))); @@ -13456,7 +13671,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -13806,12 +14021,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set util_sha512h"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 2875); // invoke the hook env(pay(bob, alice, XRP(1)), M("test util_sha512h"), fee(XRP(1))); @@ -13829,7 +14046,7 @@ public: env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) @@ -13920,12 +14137,14 @@ public: accept(0,0,0); } )[test.hook]"]; + HASH_WASM(hook); // install the hook on alice - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set util_verify"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 230); // invoke the hook env(pay(bob, alice, XRP(1)), M("test util_verify"), fee(XRP(1))); @@ -13950,7 +14169,7 @@ public: env.fund(XRP(10000), hookacc); env.close(); - TestHook hook = wasm[R"[test.hook]( + TestHook hook_wasm = wasm[R"[test.hook]( #include extern int32_t _g(uint32_t, uint32_t); extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); @@ -14279,6 +14498,7 @@ public: } } )[test.hook]"]; + HASH_WASM(hook); bool const hasFeature = env.current()->rules().enabled(featureHookCanEmit); @@ -14314,15 +14534,16 @@ public: if (i == 1) { - Json::Value h = hso(hook, overrideFlag); + Json::Value h = hso(hook_wasm, overrideFlag); env(ripple::test::jtx::hook(hookacc, {{h}}, 0), M("set hookcanemit"), HSFEE); env.close(); + EXPECT_HOOK_FEE(hook, 755); } else if (i == 2) { - Json::Value h = hso(hook, overrideFlag); + Json::Value h = hso(hook_wasm, overrideFlag); env(ripple::test::jtx::hook(acc, {{h}}, 0), M("set hookcanemit"), HSFEE); @@ -14330,7 +14551,7 @@ public: } { - Json::Value h = hso(hook, overrideFlag); + Json::Value h = hso(hook_wasm, overrideFlag); env(ripple::test::jtx::hook(acc, {{h}}, 0), M("set hookcanemit"), HSFEE); @@ -14362,7 +14583,7 @@ public: { // same result with no-HookCanEmit - Json::Value h = hso(hook, overrideFlag); + Json::Value h = hso(hook_wasm, overrideFlag); h[jss::HookCanEmit] = "0000000000000000000000000000000000000000000000000000000000" "400000"; @@ -14401,7 +14622,7 @@ public: { // install the hook on acc - Json::Value hookCanEmitHook = hso(hook, overrideFlag); + Json::Value hookCanEmitHook = hso(hook_wasm, overrideFlag); hookCanEmitHook[jss::HookCanEmit] = "00000000000000000000000000000000000000000000000000" "00000000000000"; @@ -14440,7 +14661,7 @@ public: { // install the hook on acc - Json::Value hookCanEmitHook = hso(hook, overrideFlag); + Json::Value hookCanEmitHook = hso(hook_wasm, overrideFlag); hookCanEmitHook[jss::HookCanEmit] = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" "FFFFFFFFFFFFFF"; From 1008508c9b3f8895a673da82135d67c8db4ace69 Mon Sep 17 00:00:00 2001 From: tequ Date: Tue, 17 Feb 2026 10:37:12 +0900 Subject: [PATCH 06/18] Updated tests to align with the changes merged into the dev branch. --- src/test/app/SetHook_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 16c06d7be..176cf41c2 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -3961,7 +3961,7 @@ public: M("set etxn_details"), HSFEE); env.close(); - EXPECT_HOOK_FEE(hook, 88); + EXPECT_HOOK_FEE(hook, 2436); // invoke the hook env(pay(bob, alice, XRP(1)), M("test etxn_details"), fee(XRP(1))); @@ -4131,7 +4131,7 @@ public: M("set etxn_nonce"), HSFEE); env.close(); - EXPECT_HOOK_FEE(hook, 11644); + EXPECT_HOOK_FEE(hook, 11657); // invoke the hook env(pay(bob, alice, XRP(1)), M("test etxn_nonce"), fee(XRP(1))); @@ -7884,7 +7884,7 @@ public: HASH_WASM(hook); // before featureHooksUpdate1 - env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0), M("set xpop_slot (disabled)"), HSFEE, ter(temMALFORMED)); From 3111ecea5250a1812b59a0fe677f04589477f0b0 Mon Sep 17 00:00:00 2001 From: tequ Date: Tue, 24 Feb 2026 17:23:11 +0900 Subject: [PATCH 07/18] Update util_keylet fee test --- src/test/app/SetHook_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 176cf41c2..b36d18989 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -13198,7 +13198,7 @@ public: M("set util_keylet"), HSFEE); env.close(); - EXPECT_HOOK_FEE(hook, 1546); + EXPECT_HOOK_FEE(hook, 1786); // invoke the hook env(pay(bob, alice, XRP(1)), M("test util_keylet"), fee(XRP(1))); From 6aabbc940b6fe1a1cf09732b06006e32eb9e28eb Mon Sep 17 00:00:00 2001 From: tequ Date: Thu, 5 Mar 2026 19:19:42 +0900 Subject: [PATCH 08/18] fix: typo `SignersListSet` --- src/test/app/BaseFee_test.cpp | 6 ++--- src/test/app/Import_json.h | 18 ++++++------- src/test/app/Import_test.cpp | 46 ++++++++++++++++---------------- src/test/app/SetHookTSH_test.cpp | 12 ++++----- src/test/app/Touch_test.cpp | 8 +++--- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/test/app/BaseFee_test.cpp b/src/test/app/BaseFee_test.cpp index b6600e40f..97de93631 100644 --- a/src/test/app/BaseFee_test.cpp +++ b/src/test/app/BaseFee_test.cpp @@ -582,9 +582,9 @@ class BaseFee_test : public beast::unit_test::suite } void - testSignersListSet(FeatureBitset features) + testSignerListSet(FeatureBitset features) { - testcase("signers list set w/ hook params"); + testcase("signer list set w/ hook params"); using namespace test::jtx; using namespace std::literals; @@ -810,7 +810,7 @@ class BaseFee_test : public beast::unit_test::suite testPaymentChannelFund(features); testSetHook(features); testSetRegularKey(features); - testSignersListSet(features); + testSignerListSet(features); testTicketCreate(features); testTrustSet(features); testURITokenBurnFee(features); diff --git a/src/test/app/Import_json.h b/src/test/app/Import_json.h index 37cd6363f..22982ea01 100644 --- a/src/test/app/Import_json.h +++ b/src/test/app/Import_json.h @@ -879,7 +879,7 @@ inline std::string ImportTCSetRegularKey::w_signers = R"json({ } })json"; -class ImportTCSignersListSet +class ImportTCSignerListSet { public: static std::string w_seed_bad_fee; @@ -891,7 +891,7 @@ public: static std::string w_signers_empty; }; -inline std::string ImportTCSignersListSet::w_seed_bad_fee = R"json({ +inline std::string ImportTCSignerListSet::w_seed_bad_fee = R"json({ "ledger": { "acroot": "64F75A08037D9F8ED8A103893401EB2AD726E7D6AAC3EAA249005916A9354892", "close": 743008501, @@ -952,7 +952,7 @@ inline std::string ImportTCSignersListSet::w_seed_bad_fee = R"json({ } } })json"; -inline std::string ImportTCSignersListSet::w_seed = R"json({ +inline std::string ImportTCSignerListSet::w_seed = R"json({ "ledger": { "acroot": "8112FF5F3FEEA34894A16CCCD64A24D552521F2E699780A587A9E6F5F5117CE5", "close": 743008510, @@ -993,7 +993,7 @@ inline std::string ImportTCSignersListSet::w_seed = R"json({ } } })json"; -inline std::string ImportTCSignersListSet::w_regular_key = R"json({ +inline std::string ImportTCSignerListSet::w_regular_key = R"json({ "ledger": { "acroot": "2A25CA219781A3144C72FD5FB6EB62763214E050050DA6176624A046C51EECBD", "close": 743015350, @@ -1034,7 +1034,7 @@ inline std::string ImportTCSignersListSet::w_regular_key = R"json({ } } })json"; -inline std::string ImportTCSignersListSet::w_signers = R"json({ +inline std::string ImportTCSignerListSet::w_signers = R"json({ "ledger": { "acroot": "BC35E65B52724CF258BDAC8B8E0D3B9CA0F012F5B243F6AAD1B671EDABD5188E", "close": 745594953, @@ -1075,7 +1075,7 @@ inline std::string ImportTCSignersListSet::w_signers = R"json({ } } })json"; -inline std::string ImportTCSignersListSet::w_seed_empty = R"json({ +inline std::string ImportTCSignerListSet::w_seed_empty = R"json({ "ledger": { "acroot": "ECCAFDE52A6D5F1E36EB82EAA5247FF1D8ADE51FCF1ED0842850193018A510F7", "close": 743056482, @@ -1116,7 +1116,7 @@ inline std::string ImportTCSignersListSet::w_seed_empty = R"json({ } } })json"; -inline std::string ImportTCSignersListSet::w_regular_key_empty = R"json({ +inline std::string ImportTCSignerListSet::w_regular_key_empty = R"json({ "ledger": { "acroot": "E222F46D5F35C79FDA3BB98973E2024EF9F6FA7B26471CC9CEF2CE033FA0E6E7", "close": 743169800, @@ -1157,7 +1157,7 @@ inline std::string ImportTCSignersListSet::w_regular_key_empty = R"json({ } } })json"; -inline std::string ImportTCSignersListSet::w_signers_empty = R"json({ +inline std::string ImportTCSignerListSet::w_signers_empty = R"json({ "ledger": { "acroot": "987438A87AD998B7D7ED04A280FB5414C76E8475D621A55FB8463F15CEEEAD49", "close": 743172592, @@ -1261,4 +1261,4 @@ inline std::string ImportTCHalving::base_genesis = R"json({ } // namespace test } // namespace ripple -#endif \ No newline at end of file +#endif diff --git a/src/test/app/Import_test.cpp b/src/test/app/Import_test.cpp index f9fb090be..1a65de486 100644 --- a/src/test/app/Import_test.cpp +++ b/src/test/app/Import_test.cpp @@ -1898,7 +1898,7 @@ class Import_test : public beast::unit_test::suite // different keys. { auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_signers); + import::loadXpop(ImportTCSignerListSet::w_signers); env(import::import(alice, xpopJson), msig(bob, dave), fee((3 * feeDrops) * 10), @@ -1910,7 +1910,7 @@ class Import_test : public beast::unit_test::suite // different keys. - empty innerSigners { Json::Value xpopJson = - import::loadXpop(ImportTCSignersListSet::w_signers); + import::loadXpop(ImportTCSignerListSet::w_signers); xpopJson[jss::transaction][jss::blob] = "12000C22000000002400000014201B0000002B201D00005359202300000002" "6840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F83" @@ -1927,7 +1927,7 @@ class Import_test : public beast::unit_test::suite // different keys. { Json::Value xpopJson = - import::loadXpop(ImportTCSignersListSet::w_signers); + import::loadXpop(ImportTCSignerListSet::w_signers); xpopJson[jss::transaction][jss::blob] = "12000C22000000002400000014201B0000002B201D00005359202300000002" "6840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F83" @@ -1953,7 +1953,7 @@ class Import_test : public beast::unit_test::suite // temMALFORMED - Import: inner txn signature verify failed { Json::Value xpopJson = - import::loadXpop(ImportTCSignersListSet::w_signers); + import::loadXpop(ImportTCSignerListSet::w_signers); xpopJson[jss::transaction][jss::blob] = "12000C2200000008240000001A201B000003B9201D00005359202300000000" "6840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F83" @@ -2768,7 +2768,7 @@ class Import_test : public beast::unit_test::suite env.close(); } - // tefIMPORT_BLACKHOLED - SignersListSet (w/seed) + // tefIMPORT_BLACKHOLED - SignerListSet (w/seed) { test::jtx::Env env{ *this, network::makeNetworkVLConfig(21337, keys)}; @@ -2792,7 +2792,7 @@ class Import_test : public beast::unit_test::suite // Import with Master Key Json::Value tmpXpop = - import::loadXpop(ImportTCSignersListSet::w_seed); + import::loadXpop(ImportTCSignerListSet::w_seed); env(import::import(alice, tmpXpop), ter(tefIMPORT_BLACKHOLED), fee(feeDrops * 10), @@ -3244,7 +3244,7 @@ class Import_test : public beast::unit_test::suite env(noop(alice), sig(bob), fee(feeDrops), ter(tefBAD_AUTH)); } - // w/ signers list -> dne + // w/ signer list -> dne { test::jtx::Env env{ *this, network::makeNetworkVLConfig(21337, keys)}; @@ -3975,7 +3975,7 @@ class Import_test : public beast::unit_test::suite env(noop(alice), sig(carol), fee(feeDrops), ter(tesSUCCESS)); } - // w/ signers list -> funded (update regular key) + // w/ signer list -> funded (update regular key) { test::jtx::Env env{ *this, network::makeNetworkVLConfig(21337, keys)}; @@ -4049,7 +4049,7 @@ class Import_test : public beast::unit_test::suite BEAST_EXPECT(acctSle->getAccountID(sfRegularKey) == dave.id()); env(noop(alice), sig(dave), fee(feeDrops), ter(tesSUCCESS)); - // confirm signers list not set + // confirm signer list not set auto const k = keylet::signers(alice); BEAST_EXPECT(env.current()->read(k) == nullptr); } @@ -4351,9 +4351,9 @@ class Import_test : public beast::unit_test::suite } void - testSignersListSet(FeatureBitset features) + testSignerListSet(FeatureBitset features) { - testcase("signers list set tx"); + testcase("signer list set tx"); using namespace test::jtx; using namespace std::literals; @@ -4394,7 +4394,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_seed_bad_fee); + import::loadXpop(ImportTCSignerListSet::w_seed_bad_fee); Json::Value tx = import::import(alice, xpopJson); tx[jss::Sequence] = 0; // tx[jss::Fee] = 0; @@ -4438,7 +4438,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_seed); + import::loadXpop(ImportTCSignerListSet::w_seed); Json::Value tx = import::import(alice, xpopJson); tx[jss::Sequence] = 0; tx[jss::Fee] = 0; @@ -4523,7 +4523,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const burnAmt = XRP(2); auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_regular_key); + import::loadXpop(ImportTCSignerListSet::w_regular_key); Json::Value tx = import::import(alice, xpopJson); tx[jss::Sequence] = 0; tx[jss::Fee] = 0; @@ -4614,7 +4614,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_signers); + import::loadXpop(ImportTCSignerListSet::w_signers); Json::Value tx = import::import(alice, xpopJson); tx[jss::Sequence] = 0; tx[jss::Fee] = 0; @@ -4685,7 +4685,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_seed); + import::loadXpop(ImportTCSignerListSet::w_seed); env(import::import(alice, xpopJson), fee(feeDrops * 10), ter(tesSUCCESS)); @@ -4771,7 +4771,7 @@ class Import_test : public beast::unit_test::suite auto const envAlice = env.balance(alice); BEAST_EXPECT(envAlice == XRP(1000)); - // set the signers list + // set the signer list env(signers(alice, 2, {{bob, 1}, {carol, 1}})); env(noop(alice), msig(bob, carol), @@ -4787,7 +4787,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_seed_empty); + import::loadXpop(ImportTCSignerListSet::w_seed_empty); env(import::import(alice, xpopJson), fee(feeDrops * 10), ter(tesSUCCESS)); @@ -4852,7 +4852,7 @@ class Import_test : public beast::unit_test::suite env(noop(alice), sig(bob), fee(feeDrops), ter(tesSUCCESS)); env.close(); - // set the signers list + // set the signer list env(signers(alice, 2, {{bob, 1}, {carol, 1}})); env(noop(alice), msig(bob, carol), @@ -4868,7 +4868,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_regular_key_empty); + import::loadXpop(ImportTCSignerListSet::w_regular_key_empty); env(import::import(alice, xpopJson), fee(feeDrops * 10), sig(bob), @@ -4935,7 +4935,7 @@ class Import_test : public beast::unit_test::suite auto const envAlice = env.balance(alice); BEAST_EXPECT(envAlice == XRP(1000)); - // set the signers list + // set the signer list env(signers(alice, 2, {{bob, 1}, {carol, 1}})); env(noop(alice), msig(bob, carol), @@ -4951,7 +4951,7 @@ class Import_test : public beast::unit_test::suite // import tx auto const xpopJson = - import::loadXpop(ImportTCSignersListSet::w_signers_empty); + import::loadXpop(ImportTCSignerListSet::w_signers_empty); env(import::import(alice, xpopJson), msig(bob, carol), fee((3 * feeDrops) * 10), @@ -6228,7 +6228,7 @@ public: testAccountSetFlags(features); testSetRegularKey(features); testSetRegularKeyFlags(features); - testSignersListSet(features); + testSignerListSet(features); testUsingTickets(features); testAccountIndex(features); testHookIssuer(features); diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 425eedbaf..223a200b8 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -4492,14 +4492,14 @@ private: } } - // SignersListSet + // SignerListSet // | otxn | tsh | sls | // | A | A | S | // | A | S | S | void - testSignersListSetTSH(FeatureBitset features) + testSignerListSetTSH(FeatureBitset features) { - testcase("signers list set tsh"); + testcase("signer list set tsh"); using namespace test::jtx; using namespace std::literals; @@ -4527,7 +4527,7 @@ private: // set tsh hook setTSHHook(env, account, testStrong); - // signers list set + // signer list set env(signers(account, 2, {{signer1, 1}, {signer2, 1}}), fee(XRP(1)), ter(tesSUCCESS)); @@ -4566,7 +4566,7 @@ private: // set tsh hook setTSHHook(env, signer2, testStrong); - // signers list set + // signer list set env(signers(account, 2, {{signer1, 1}, {signer2, 1}}), fee(XRP(1)), ter(tesSUCCESS)); @@ -6914,7 +6914,7 @@ private: testPaymentChannelFundTSH(features); testSetHookTSH(features); testSetRegularKeyTSH(features); - testSignersListSetTSH(features); + testSignerListSetTSH(features); testTicketCreateTSH(features); testTrustSetTSH(features); testURITokenMintTSH(features); diff --git a/src/test/app/Touch_test.cpp b/src/test/app/Touch_test.cpp index 6943e5d37..f3a545f5d 100644 --- a/src/test/app/Touch_test.cpp +++ b/src/test/app/Touch_test.cpp @@ -880,9 +880,9 @@ private: } void - testSignersListSet(FeatureBitset features) + testSignerListSet(FeatureBitset features) { - testcase("signers list set"); + testcase("signer list set"); using namespace test::jtx; using namespace std::literals; @@ -895,7 +895,7 @@ private: env.fund(XRP(1000), alice, signer1, signer2); env.close(); - // signers list set + // signer list set env(signers(alice, 2, {{signer1, 1}, {signer2, 1}}), ter(tesSUCCESS)); env.close(); @@ -1384,7 +1384,7 @@ private: testPaymentChannelFund(features); testSetHook(features); testSetRegularKey(features); - testSignersListSet(features); + testSignerListSet(features); testTicketCreate(features); testTrustSet(features); testURITokenMint(features); From 52843e2321a7ae656513105ca97901305c9d88bc Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 28 Nov 2025 01:10:49 +0900 Subject: [PATCH 09/18] output ccache configuration in release-builder --- build-core.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build-core.sh b/build-core.sh index 7054ce4ed..e6867d5e2 100755 --- a/build-core.sh +++ b/build-core.sh @@ -71,6 +71,7 @@ cmake .. -G Ninja \ -Dxrpld=TRUE \ -Dtests=TRUE && ccache -z && +ccache -p && ninja -j $3 && echo "=== Re-running final link with verbose output ===" && rm -f rippled && ninja -v rippled && ccache -s && strip -s rippled && From 1d7c7e5603ae6536f17e5074c2b608ca226ff8d9 Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 28 Nov 2025 01:31:26 +0900 Subject: [PATCH 10/18] enable ccache direct_mode --- release-builder.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/release-builder.sh b/release-builder.sh index 436c8cd4a..ac9c46981 100755 --- a/release-builder.sh +++ b/release-builder.sh @@ -196,6 +196,7 @@ ENV PATH=/usr/local/bin:$PATH RUN /hbb_exe/activate-exec bash -c "ccache -M 100G && \ ccache -o cache_dir=/cache/ccache && \ ccache -o compiler_check=content && \ + ccache -o direct_mode=true && \ mkdir -p ~/.conan2 /cache/conan2 /cache/conan2_download /cache/conan2_sources && \ echo 'core.cache:storage_path=/cache/conan2' > ~/.conan2/global.conf && \ echo 'core.download:download_cache=/cache/conan2_download' >> ~/.conan2/global.conf && \ From dbbffd917e2ad22393b1369939402bc42d3ec54e Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Fri, 13 Mar 2026 12:08:27 +0700 Subject: [PATCH 11/18] chore: replace levelization shell script with python Backport of XRPLF/rippled#6325. The python version runs ~80x faster. --- .github/workflows/levelization.yml | 4 +- .gitignore | 3 + Builds/levelization/README.md | 6 +- Builds/levelization/levelization.py | 283 ++++++++++++++++++++++++++++ Builds/levelization/levelization.sh | 130 ------------- 5 files changed, 291 insertions(+), 135 deletions(-) create mode 100755 Builds/levelization/levelization.py delete mode 100755 Builds/levelization/levelization.sh diff --git a/.github/workflows/levelization.yml b/.github/workflows/levelization.yml index f99c1ca56..029672e18 100644 --- a/.github/workflows/levelization.yml +++ b/.github/workflows/levelization.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Check levelization - run: Builds/levelization/levelization.sh + run: python Builds/levelization/levelization.py - name: Check for differences id: assert run: | @@ -40,7 +40,7 @@ jobs: To fix it, you can do one of two things: 1. Download and apply the patch generated as an artifact of this job to your repo, commit, and push. - 2. Run './Builds/levelization/levelization.sh' in your repo, + 2. Run 'python Builds/levelization/levelization.py' in your repo, commit, and push. See Builds/levelization/README.md for more info. diff --git a/.gitignore b/.gitignore index 585a69efb..121cba965 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ Builds/levelization/results/paths.txt Builds/levelization/results/includes/ Builds/levelization/results/includedby/ +# Python +__pycache__ + # Ignore tmp directory. tmp diff --git a/Builds/levelization/README.md b/Builds/levelization/README.md index 4ff3a5423..430247558 100644 --- a/Builds/levelization/README.md +++ b/Builds/levelization/README.md @@ -50,7 +50,7 @@ that `test` code should *never* be included in `ripple` code.) ## Validation -The [levelization.sh](levelization.sh) script takes no parameters, +The [levelization.py](levelization.py) script takes no parameters, reads no environment variables, and can be run from any directory, as long as it is in the expected location in the rippled repo. It can be run at any time from within a checked out repo, and will @@ -84,7 +84,7 @@ It generates many files of [results](results): Github Actions workflow to test that levelization loops haven't changed. Unfortunately, if changes are detected, it can't tell if they are improvements or not, so if you have resolved any issues or - done anything else to improve levelization, run `levelization.sh`, + done anything else to improve levelization, run `levelization.py`, and commit the updated results. The `loops.txt` and `ordering.txt` files relate the modules @@ -108,7 +108,7 @@ The committed files hide the detailed values intentionally, to prevent false alarms and merging issues, and because it's easy to get those details locally. -1. Run `levelization.sh` +1. Run `levelization.py` 2. Grep the modules in `paths.txt`. * For example, if a cycle is found `A ~= B`, simply `grep -w A Builds/levelization/results/paths.txt | grep -w B` diff --git a/Builds/levelization/levelization.py b/Builds/levelization/levelization.py new file mode 100755 index 000000000..043c9e00d --- /dev/null +++ b/Builds/levelization/levelization.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 + +""" +Usage: levelization.py +This script takes no parameters, and can be called from any directory in the file system. +""" + +import os +import re +import sys +from collections import defaultdict +from pathlib import Path + +# Compile regex patterns once at module level +INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h") +INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]') + + +def dictionary_sort_key(s): + """ + Create a sort key that mimics 'sort -d' (dictionary order). + Dictionary order only considers blanks and alphanumeric characters. + """ + return "".join(c for c in s if c.isalnum() or c.isspace()) + + +def get_level(file_path): + """ + Extract the level from a file path (second and third directory components). + Equivalent to bash: cut -d/ -f 2,3 + + Examples: + src/ripple/app/main.cpp -> ripple.app + src/test/app/Import_test.cpp -> test.app + """ + parts = file_path.split("/") + + if len(parts) >= 3: + level = f"{parts[1]}/{parts[2]}" + elif len(parts) >= 2: + level = f"{parts[1]}/toplevel" + else: + level = file_path + + # If the "level" indicates a file, cut off the filename + if "." in level.split("/")[-1]: + # Use the "toplevel" label as a workaround for `sort` + # inconsistencies between different utility versions + level = level.rsplit("/", 1)[0] + "/toplevel" + + return level.replace("/", ".") + + +def extract_include_level(include_line): + """ + Extract the include path from an #include directive. + Gets the first two directory components from the include path. + Equivalent to bash: cut -d/ -f 1,2 + + Examples: + #include -> ripple.basics + #include "ripple/app/main/Application.h" -> ripple.app + """ + match = INCLUDE_PATH_PATTERN.search(include_line) + if not match: + return None + + include_path = match.group(1) + parts = include_path.split("/") + + if len(parts) >= 2: + include_level = f"{parts[0]}/{parts[1]}" + else: + include_level = include_path + + # If the "includelevel" indicates a file, cut off the filename + if "." in include_level.split("/")[-1]: + include_level = include_level.rsplit("/", 1)[0] + "/toplevel" + + return include_level.replace("/", ".") + + +def find_repository_directories(start_path, depth_limit=10): + """ + Find the repository root by looking for src or include folders. + Walks up the directory tree from the start path. + """ + current = start_path.resolve() + + for _ in range(depth_limit): + src_path = current / "src" + include_path = current / "include" + has_src = src_path.exists() + has_include = include_path.exists() + + if has_src or has_include: + dirs = [] + if has_src: + dirs.append(src_path) + if has_include: + dirs.append(include_path) + return current, dirs + + parent = current.parent + if parent == current: + break + current = parent + + raise RuntimeError( + "Could not find repository root. " + "Expected to find a directory containing 'src' and/or 'include' folders." + ) + + +def main(): + script_dir = Path(__file__).parent.resolve() + os.chdir(script_dir) + + # Clean up and create results directory. + results_dir = script_dir / "results" + if results_dir.exists(): + import shutil + + shutil.rmtree(results_dir) + results_dir.mkdir() + + # Find the repository root. + try: + repo_root, scan_dirs = find_repository_directories(script_dir) + print(f"Found repository root: {repo_root}") + for scan_dir in scan_dirs: + print(f" Scanning: {scan_dir.relative_to(repo_root)}") + except RuntimeError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + # Find all #include directives. + print("\nScanning for raw includes...") + raw_includes = [] + rawincludes_file = results_dir / "rawincludes.txt" + + with open(rawincludes_file, "w", buffering=8192) as raw_f: + for dir_path in scan_dirs: + for file_path in dir_path.rglob("*"): + if not file_path.is_file(): + continue + try: + rel_path_str = str(file_path.relative_to(repo_root)) + with open( + file_path, "r", encoding="utf-8", errors="ignore", buffering=8192 + ) as f: + for line in f: + if "#include" not in line or "boost" in line: + continue + if INCLUDE_PATTERN.match(line): + line_stripped = line.strip() + entry = f"{rel_path_str}:{line_stripped}\n" + print(entry, end="") + raw_f.write(entry) + raw_includes.append((rel_path_str, line_stripped)) + except Exception as e: + print(f"Error reading {file_path}: {e}", file=sys.stderr) + + # Build levelization paths and count directly. + print("Build levelization paths") + path_counts = defaultdict(int) + + for file_path, include_line in raw_includes: + include_level = extract_include_level(include_line) + if not include_level: + continue + level = get_level(file_path) + if level != include_level: + path_counts[(level, include_level)] += 1 + + # Sort and deduplicate paths. + print("Sort and deduplicate paths") + sorted_items = sorted( + path_counts.items(), + key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])), + ) + + paths_file = results_dir / "paths.txt" + with open(paths_file, "w") as f: + for (level, include_level), count in sorted_items: + line = f"{count:7} {level} {include_level}\n" + print(line.rstrip()) + f.write(line) + + # Split into flat-file database. + print("Split into flat-file database") + includes_dir = results_dir / "includes" + includedby_dir = results_dir / "includedby" + includes_dir.mkdir() + includedby_dir.mkdir() + + includes_data = defaultdict(list) + includedby_data = defaultdict(list) + + for (level, include_level), count in sorted_items: + includes_data[level].append((include_level, count)) + includedby_data[include_level].append((level, count)) + + for level in sorted(includes_data.keys(), key=dictionary_sort_key): + with open(includes_dir / level, "w") as f: + for include_level, count in includes_data[level]: + line = f"{include_level} {count}\n" + print(line.rstrip()) + f.write(line) + + for include_level in sorted(includedby_data.keys(), key=dictionary_sort_key): + with open(includedby_dir / include_level, "w") as f: + for level, count in includedby_data[include_level]: + line = f"{level} {count}\n" + print(line.rstrip()) + f.write(line) + + # Search for loops. + print("Search for loops") + loops_file = results_dir / "loops.txt" + ordering_file = results_dir / "ordering.txt" + + # Pre-load all include files into memory for fast lookup. + includes_cache = {} + includes_lookup = {} + + for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name): + if not include_file.is_file(): + continue + includes_cache[include_file.name] = [] + includes_lookup[include_file.name] = {} + with open(include_file, "r") as f: + for line in f: + parts = line.strip().split() + if len(parts) >= 2: + name, count = parts[0], int(parts[1]) + includes_cache[include_file.name].append((name, count)) + includes_lookup[include_file.name][name] = count + + loops_found = set() + + with open(loops_file, "w", buffering=8192) as loops_f, open( + ordering_file, "w", buffering=8192 + ) as ordering_f: + for source in sorted(includes_cache.keys()): + for include, include_freq in includes_cache[source]: + if include not in includes_lookup: + continue + + source_freq = includes_lookup[include].get(source) + + if source_freq is not None: + loop_key = tuple(sorted([source, include])) + if loop_key in loops_found: + continue + loops_found.add(loop_key) + + loops_f.write(f"Loop: {source} {include}\n") + + diff = include_freq - source_freq + if diff > 3: + loops_f.write(f" {source} > {include}\n\n") + elif diff < -3: + loops_f.write(f" {include} > {source}\n\n") + elif source_freq == include_freq: + loops_f.write(f" {include} == {source}\n\n") + else: + loops_f.write(f" {include} ~= {source}\n\n") + else: + ordering_f.write(f"{source} > {include}\n") + + # Print results. + print("\nOrdering:") + with open(ordering_file, "r") as f: + print(f.read(), end="") + + print("\nLoops:") + with open(loops_file, "r") as f: + print(f.read(), end="") + + +if __name__ == "__main__": + main() diff --git a/Builds/levelization/levelization.sh b/Builds/levelization/levelization.sh deleted file mode 100755 index c18ca703f..000000000 --- a/Builds/levelization/levelization.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/bash - -# Usage: levelization.sh -# This script takes no parameters, reads no environment variables, -# and can be run from any directory, as long as it is in the expected -# location in the repo. - -pushd $( dirname $0 ) - -if [ -v PS1 ] -then - # if the shell is interactive, clean up any flotsam before analyzing - git clean -ix -fi - -# Ensure all sorting is ASCII-order consistently across platforms. -export LANG=C - -rm -rfv results -mkdir results -includes="$( pwd )/results/rawincludes.txt" -pushd ../.. -echo Raw includes: -grep -r '^[ ]*#include.*/.*\.h' include src | \ - grep -v boost | tee ${includes} -popd -pushd results - -oldifs=${IFS} -IFS=: -mkdir includes -mkdir includedby -echo Build levelization paths -exec 3< ${includes} # open rawincludes.txt for input -while read -r -u 3 file include -do - level=$( echo ${file} | cut -d/ -f 2,3 ) - # If the "level" indicates a file, cut off the filename - if [[ "${level##*.}" != "${level}" ]] - then - # Use the "toplevel" label as a workaround for `sort` - # inconsistencies between different utility versions - level="$( dirname ${level} )/toplevel" - fi - level=$( echo ${level} | tr '/' '.' ) - - includelevel=$( echo ${include} | sed 's/.*["<]//; s/[">].*//' | \ - cut -d/ -f 1,2 ) - if [[ "${includelevel##*.}" != "${includelevel}" ]] - then - # Use the "toplevel" label as a workaround for `sort` - # inconsistencies between different utility versions - includelevel="$( dirname ${includelevel} )/toplevel" - fi - includelevel=$( echo ${includelevel} | tr '/' '.' ) - - if [[ "$level" != "$includelevel" ]] - then - echo $level $includelevel | tee -a paths.txt - fi -done -echo Sort and dedup paths -sort -ds paths.txt | uniq -c | tee sortedpaths.txt -mv sortedpaths.txt paths.txt -exec 3>&- #close fd 3 -IFS=${oldifs} -unset oldifs - -echo Split into flat-file database -exec 4&- #close fd 4 - -loops="$( pwd )/loops.txt" -ordering="$( pwd )/ordering.txt" -pushd includes -echo Search for loops -# Redirect stdout to a file -exec 4>&1 -exec 1>"${loops}" -for source in * -do - if [[ -f "$source" ]] - then - exec 5<"${source}" # open for input - while read -r -u 5 include includefreq - do - if [[ -f $include ]] - then - if grep -q -w $source $include - then - if grep -q -w "Loop: $include $source" "${loops}" - then - continue - fi - sourcefreq=$( grep -w $source $include | cut -d\ -f2 ) - echo "Loop: $source $include" - # If the counts are close, indicate that the two modules are - # on the same level, though they shouldn't be - if [[ $(( $includefreq - $sourcefreq )) -gt 3 ]] - then - echo -e " $source > $include\n" - elif [[ $(( $sourcefreq - $includefreq )) -gt 3 ]] - then - echo -e " $include > $source\n" - elif [[ $sourcefreq -eq $includefreq ]] - then - echo -e " $include == $source\n" - else - echo -e " $include ~= $source\n" - fi - else - echo "$source > $include" >> "${ordering}" - fi - fi - done - exec 5>&- #close fd 5 - fi -done -exec 1>&4 #close fd 1 -exec 4>&- #close fd 4 -cat "${ordering}" -cat "${loops}" -popd -popd -popd From ad9d6a6eb33b1920226a17c5129f13c5378b5940 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Fri, 13 Mar 2026 12:13:39 +0700 Subject: [PATCH 12/18] chore: use improved levelization script with threading and argparse --- Builds/levelization/levelization.py | 456 ++++++++++++++-------------- 1 file changed, 223 insertions(+), 233 deletions(-) diff --git a/Builds/levelization/levelization.py b/Builds/levelization/levelization.py index 043c9e00d..964acdcd4 100755 --- a/Builds/levelization/levelization.py +++ b/Builds/levelization/levelization.py @@ -1,283 +1,273 @@ #!/usr/bin/env python3 - """ -Usage: levelization.py -This script takes no parameters, and can be called from any directory in the file system. +Levelization generator. + +Produces the same result artifacts as levelization.sh, but much faster by +doing parsing/counting in-process instead of spawning external tools in +tight loops. """ +from __future__ import annotations + +import argparse +import concurrent.futures import os +import posixpath import re -import sys -from collections import defaultdict +import shutil +import time +from collections import Counter, defaultdict from pathlib import Path -# Compile regex patterns once at module level INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h") -INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]') +INCLUDE_TARGET_PATTERN = re.compile(r'.*["<]([^">]+)[">].*') +PATHS_LINE_PATTERN = re.compile(r"^\s*(\d+)\s+(\S+)\s+(\S+)\s*$") -def dictionary_sort_key(s): - """ - Create a sort key that mimics 'sort -d' (dictionary order). - Dictionary order only considers blanks and alphanumeric characters. - """ - return "".join(c for c in s if c.isalnum() or c.isspace()) +def dictionary_sort_key(value: str) -> str: + """Approximate `sort -d` behavior used by the shell script.""" + return "".join(ch for ch in value if ch.isalnum() or ch.isspace()) -def get_level(file_path): - """ - Extract the level from a file path (second and third directory components). - Equivalent to bash: cut -d/ -f 2,3 - - Examples: - src/ripple/app/main.cpp -> ripple.app - src/test/app/Import_test.cpp -> test.app - """ - parts = file_path.split("/") - - if len(parts) >= 3: - level = f"{parts[1]}/{parts[2]}" - elif len(parts) >= 2: - level = f"{parts[1]}/toplevel" - else: - level = file_path - - # If the "level" indicates a file, cut off the filename - if "." in level.split("/")[-1]: - # Use the "toplevel" label as a workaround for `sort` - # inconsistencies between different utility versions - level = level.rsplit("/", 1)[0] + "/toplevel" - - return level.replace("/", ".") +def normalize_level(value: str) -> str: + # Match shell behavior: if level includes a file component (contains "."), + # replace with dirname + "/toplevel". + if "." in value: + parent = posixpath.dirname(value) or "." + value = f"{parent}/toplevel" + return value.replace("/", ".") -def extract_include_level(include_line): - """ - Extract the include path from an #include directive. - Gets the first two directory components from the include path. - Equivalent to bash: cut -d/ -f 1,2 +def source_level(rel_path: str) -> str: + parts = rel_path.split("/") + return normalize_level("/".join(parts[1:3])) - Examples: - #include -> ripple.basics - #include "ripple/app/main/Application.h" -> ripple.app - """ - match = INCLUDE_PATH_PATTERN.search(include_line) + +def include_level(include_line: str) -> str | None: + match = INCLUDE_TARGET_PATTERN.match(include_line) if not match: return None - include_path = match.group(1) parts = include_path.split("/") - - if len(parts) >= 2: - include_level = f"{parts[0]}/{parts[1]}" - else: - include_level = include_path - - # If the "includelevel" indicates a file, cut off the filename - if "." in include_level.split("/")[-1]: - include_level = include_level.rsplit("/", 1)[0] + "/toplevel" - - return include_level.replace("/", ".") + return normalize_level("/".join(parts[:2])) -def find_repository_directories(start_path, depth_limit=10): - """ - Find the repository root by looking for src or include folders. - Walks up the directory tree from the start path. - """ - current = start_path.resolve() +def scan_file(path: Path, repo_root: Path) -> tuple[list[str], list[tuple[str, str]]]: + rel = path.relative_to(repo_root).as_posix() + src_level = source_level(rel) - for _ in range(depth_limit): - src_path = current / "src" - include_path = current / "include" - has_src = src_path.exists() - has_include = include_path.exists() + raw_lines: list[str] = [] + paths: list[tuple[str, str]] = [] - if has_src or has_include: - dirs = [] - if has_src: - dirs.append(src_path) - if has_include: - dirs.append(include_path) - return current, dirs + with path.open("r", encoding="utf-8", errors="ignore") as handle: + for line in handle: + if "boost" in line: + continue + if not INCLUDE_PATTERN.match(line): + continue - parent = current.parent - if parent == current: - break - current = parent + line = line.rstrip("\n") + raw_lines.append(f"{rel}:{line}") - raise RuntimeError( - "Could not find repository root. " - "Expected to find a directory containing 'src' and/or 'include' folders." - ) + dst_level = include_level(line) + if dst_level is None: + continue + if src_level != dst_level: + paths.append((src_level, dst_level)) + + return raw_lines, paths -def main(): - script_dir = Path(__file__).parent.resolve() - os.chdir(script_dir) +def iter_source_files(repo_root: Path) -> list[Path]: + files: list[Path] = [] + for top in ("include", "src"): + root = repo_root / top + if root.exists(): + files.extend(path for path in root.rglob("*") if path.is_file()) + files.sort(key=lambda p: p.relative_to(repo_root).as_posix()) + return files - # Clean up and create results directory. - results_dir = script_dir / "results" - if results_dir.exists(): - import shutil - shutil.rmtree(results_dir) - results_dir.mkdir() - - # Find the repository root. - try: - repo_root, scan_dirs = find_repository_directories(script_dir) - print(f"Found repository root: {repo_root}") - for scan_dir in scan_dirs: - print(f" Scanning: {scan_dir.relative_to(repo_root)}") - except RuntimeError as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - # Find all #include directives. - print("\nScanning for raw includes...") - raw_includes = [] - rawincludes_file = results_dir / "rawincludes.txt" - - with open(rawincludes_file, "w", buffering=8192) as raw_f: - for dir_path in scan_dirs: - for file_path in dir_path.rglob("*"): - if not file_path.is_file(): - continue - try: - rel_path_str = str(file_path.relative_to(repo_root)) - with open( - file_path, "r", encoding="utf-8", errors="ignore", buffering=8192 - ) as f: - for line in f: - if "#include" not in line or "boost" in line: - continue - if INCLUDE_PATTERN.match(line): - line_stripped = line.strip() - entry = f"{rel_path_str}:{line_stripped}\n" - print(entry, end="") - raw_f.write(entry) - raw_includes.append((rel_path_str, line_stripped)) - except Exception as e: - print(f"Error reading {file_path}: {e}", file=sys.stderr) - - # Build levelization paths and count directly. - print("Build levelization paths") - path_counts = defaultdict(int) - - for file_path, include_line in raw_includes: - include_level = extract_include_level(include_line) - if not include_level: - continue - level = get_level(file_path) - if level != include_level: - path_counts[(level, include_level)] += 1 - - # Sort and deduplicate paths. - print("Sort and deduplicate paths") - sorted_items = sorted( - path_counts.items(), - key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])), - ) - - paths_file = results_dir / "paths.txt" - with open(paths_file, "w") as f: - for (level, include_level), count in sorted_items: - line = f"{count:7} {level} {include_level}\n" - print(line.rstrip()) - f.write(line) - - # Split into flat-file database. - print("Split into flat-file database") +def write_relation_db( + results_dir: Path, + edge_counts: list[tuple[tuple[str, str], int]], +) -> tuple[dict[str, list[tuple[str, int]]], dict[str, list[tuple[str, int]]]]: includes_dir = results_dir / "includes" includedby_dir = results_dir / "includedby" - includes_dir.mkdir() - includedby_dir.mkdir() + includes_dir.mkdir(parents=True, exist_ok=True) + includedby_dir.mkdir(parents=True, exist_ok=True) - includes_data = defaultdict(list) - includedby_data = defaultdict(list) + includes: dict[str, list[tuple[str, int]]] = defaultdict(list) + includedby: dict[str, list[tuple[str, int]]] = defaultdict(list) - for (level, include_level), count in sorted_items: - includes_data[level].append((include_level, count)) - includedby_data[include_level].append((level, count)) + with (results_dir / "paths.txt").open("w", encoding="utf-8") as out: + for (src, dst), count in edge_counts: + out.write(f"{count:7d} {src} {dst}\n") + includes[src].append((dst, count)) + includedby[dst].append((src, count)) - for level in sorted(includes_data.keys(), key=dictionary_sort_key): - with open(includes_dir / level, "w") as f: - for include_level, count in includes_data[level]: - line = f"{include_level} {count}\n" - print(line.rstrip()) - f.write(line) + for src, entries in includes.items(): + with (includes_dir / src).open("w", encoding="utf-8") as out: + for dst, count in entries: + out.write(f"{dst} {count}\n") - for include_level in sorted(includedby_data.keys(), key=dictionary_sort_key): - with open(includedby_dir / include_level, "w") as f: - for level, count in includedby_data[include_level]: - line = f"{level} {count}\n" - print(line.rstrip()) - f.write(line) + for dst, entries in includedby.items(): + with (includedby_dir / dst).open("w", encoding="utf-8") as out: + for src, count in entries: + out.write(f"{src} {count}\n") - # Search for loops. - print("Search for loops") - loops_file = results_dir / "loops.txt" - ordering_file = results_dir / "ordering.txt" + return includes, includedby - # Pre-load all include files into memory for fast lookup. - includes_cache = {} - includes_lookup = {} - for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name): - if not include_file.is_file(): - continue - includes_cache[include_file.name] = [] - includes_lookup[include_file.name] = {} - with open(include_file, "r") as f: - for line in f: - parts = line.strip().split() - if len(parts) >= 2: - name, count = parts[0], int(parts[1]) - includes_cache[include_file.name].append((name, count)) - includes_lookup[include_file.name][name] = count +def build_loops_and_ordering( + includes: dict[str, list[tuple[str, int]]], +) -> tuple[list[str], list[str]]: + include_map = { + src: {dst: count for dst, count in entries} + for src, entries in includes.items() + } - loops_found = set() + ordering_lines: list[str] = [] + loops_lines: list[str] = [] - with open(loops_file, "w", buffering=8192) as loops_f, open( - ordering_file, "w", buffering=8192 - ) as ordering_f: - for source in sorted(includes_cache.keys()): - for include, include_freq in includes_cache[source]: - if include not in includes_lookup: - continue + seen_pairs: set[tuple[str, str]] = set() - source_freq = includes_lookup[include].get(source) + for source in sorted(includes.keys()): + for include, includefreq in includes[source]: + if include not in include_map: + continue - if source_freq is not None: - loop_key = tuple(sorted([source, include])) - if loop_key in loops_found: - continue - loops_found.add(loop_key) + sourcefreq = include_map[include].get(source) + if sourcefreq is None: + ordering_lines.append(f"{source} > {include}\n") + continue - loops_f.write(f"Loop: {source} {include}\n") + if (include, source) in seen_pairs: + continue + seen_pairs.add((source, include)) - diff = include_freq - source_freq - if diff > 3: - loops_f.write(f" {source} > {include}\n\n") - elif diff < -3: - loops_f.write(f" {include} > {source}\n\n") - elif source_freq == include_freq: - loops_f.write(f" {include} == {source}\n\n") - else: - loops_f.write(f" {include} ~= {source}\n\n") - else: - ordering_f.write(f"{source} > {include}\n") + loops_lines.append(f"Loop: {source} {include}\n") + if includefreq - sourcefreq > 3: + loops_lines.append(f" {source} > {include}\n\n") + elif sourcefreq - includefreq > 3: + loops_lines.append(f" {include} > {source}\n\n") + elif sourcefreq == includefreq: + loops_lines.append(f" {include} == {source}\n\n") + else: + loops_lines.append(f" {include} ~= {source}\n\n") - # Print results. - print("\nOrdering:") - with open(ordering_file, "r") as f: - print(f.read(), end="") + return ordering_lines, loops_lines - print("\nLoops:") - with open(loops_file, "r") as f: - print(f.read(), end="") + +def generate(results_dir: Path, repo_root: Path, workers: int) -> None: + if results_dir.exists(): + shutil.rmtree(results_dir) + results_dir.mkdir(parents=True) + + files = iter_source_files(repo_root) + + raw_by_file: dict[str, list[str]] = {} + paths_by_file: dict[str, list[tuple[str, str]]] = {} + + start = time.perf_counter() + if workers <= 1: + for file in files: + rel = file.relative_to(repo_root).as_posix() + raw, paths = scan_file(file, repo_root) + raw_by_file[rel] = raw + paths_by_file[rel] = paths + else: + with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool: + futures = { + file.relative_to(repo_root).as_posix(): pool.submit( + scan_file, file, repo_root + ) + for file in files + } + for rel in sorted(futures.keys()): + raw, paths = futures[rel].result() + raw_by_file[rel] = raw + paths_by_file[rel] = paths + + raw_lines: list[str] = [] + raw_lines.extend( + line + for rel in sorted(raw_by_file.keys()) + for line in raw_by_file[rel] + ) + with (results_dir / "rawincludes.txt").open("w", encoding="utf-8") as out: + out.write("\n".join(raw_lines)) + if raw_lines: + out.write("\n") + + path_pairs: list[tuple[str, str]] = [] + path_pairs.extend( + pair + for rel in sorted(paths_by_file.keys()) + for pair in paths_by_file[rel] + ) + counts = Counter(path_pairs) + + edge_counts = sorted( + counts.items(), + key=lambda item: ( + dictionary_sort_key(item[0][0]), + dictionary_sort_key(item[0][1]), + ), + ) + + includes, _ = write_relation_db(results_dir, edge_counts) + ordering, loops = build_loops_and_ordering(includes) + + with (results_dir / "ordering.txt").open("w", encoding="utf-8") as out: + out.writelines(ordering) + with (results_dir / "loops.txt").open("w", encoding="utf-8") as out: + out.writelines(loops) + + elapsed = time.perf_counter() - start + print( + f"levelization.py: scanned {len(files)} files, " + f"{len(raw_lines)} includes, {len(edge_counts)} unique paths in " + f"{elapsed:.2f}s" + ) + print((results_dir / "ordering.txt").read_text(encoding="utf-8"), end="") + print((results_dir / "loops.txt").read_text(encoding="utf-8"), end="") + + +def main() -> int: + script_dir = Path(__file__).resolve().parent + repo_root = script_dir.parents[1] + + parser = argparse.ArgumentParser() + parser.add_argument( + "--repo-root", + type=Path, + default=repo_root, + help="Repository root (defaults based on script location).", + ) + parser.add_argument( + "--results-dir", + type=Path, + default=script_dir / "results", + help="Output results directory.", + ) + parser.add_argument( + "--workers", + type=int, + default=min(32, (os.cpu_count() or 1)), + help="Thread count for source scanning (default: CPU count, max 32).", + ) + args = parser.parse_args() + + generated_dir = args.results_dir.resolve() + generate( + results_dir=generated_dir, + repo_root=args.repo_root.resolve(), + workers=max(1, args.workers), + ) + + return 0 if __name__ == "__main__": - main() + raise SystemExit(main()) From e4c7893bf077bee0ed75437135f9a177ac35fe48 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Fri, 13 Mar 2026 12:33:19 +0700 Subject: [PATCH 13/18] Revert "chore: use improved levelization script with threading and argparse" This reverts commit 5c1d7d9ae907e7ad13d58a41ccbc6ee19408b456. --- Builds/levelization/levelization.py | 456 ++++++++++++++-------------- 1 file changed, 233 insertions(+), 223 deletions(-) diff --git a/Builds/levelization/levelization.py b/Builds/levelization/levelization.py index 964acdcd4..043c9e00d 100755 --- a/Builds/levelization/levelization.py +++ b/Builds/levelization/levelization.py @@ -1,273 +1,283 @@ #!/usr/bin/env python3 -""" -Levelization generator. -Produces the same result artifacts as levelization.sh, but much faster by -doing parsing/counting in-process instead of spawning external tools in -tight loops. +""" +Usage: levelization.py +This script takes no parameters, and can be called from any directory in the file system. """ -from __future__ import annotations - -import argparse -import concurrent.futures import os -import posixpath import re -import shutil -import time -from collections import Counter, defaultdict +import sys +from collections import defaultdict from pathlib import Path +# Compile regex patterns once at module level INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h") -INCLUDE_TARGET_PATTERN = re.compile(r'.*["<]([^">]+)[">].*') -PATHS_LINE_PATTERN = re.compile(r"^\s*(\d+)\s+(\S+)\s+(\S+)\s*$") +INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]') -def dictionary_sort_key(value: str) -> str: - """Approximate `sort -d` behavior used by the shell script.""" - return "".join(ch for ch in value if ch.isalnum() or ch.isspace()) +def dictionary_sort_key(s): + """ + Create a sort key that mimics 'sort -d' (dictionary order). + Dictionary order only considers blanks and alphanumeric characters. + """ + return "".join(c for c in s if c.isalnum() or c.isspace()) -def normalize_level(value: str) -> str: - # Match shell behavior: if level includes a file component (contains "."), - # replace with dirname + "/toplevel". - if "." in value: - parent = posixpath.dirname(value) or "." - value = f"{parent}/toplevel" - return value.replace("/", ".") +def get_level(file_path): + """ + Extract the level from a file path (second and third directory components). + Equivalent to bash: cut -d/ -f 2,3 + + Examples: + src/ripple/app/main.cpp -> ripple.app + src/test/app/Import_test.cpp -> test.app + """ + parts = file_path.split("/") + + if len(parts) >= 3: + level = f"{parts[1]}/{parts[2]}" + elif len(parts) >= 2: + level = f"{parts[1]}/toplevel" + else: + level = file_path + + # If the "level" indicates a file, cut off the filename + if "." in level.split("/")[-1]: + # Use the "toplevel" label as a workaround for `sort` + # inconsistencies between different utility versions + level = level.rsplit("/", 1)[0] + "/toplevel" + + return level.replace("/", ".") -def source_level(rel_path: str) -> str: - parts = rel_path.split("/") - return normalize_level("/".join(parts[1:3])) +def extract_include_level(include_line): + """ + Extract the include path from an #include directive. + Gets the first two directory components from the include path. + Equivalent to bash: cut -d/ -f 1,2 - -def include_level(include_line: str) -> str | None: - match = INCLUDE_TARGET_PATTERN.match(include_line) + Examples: + #include -> ripple.basics + #include "ripple/app/main/Application.h" -> ripple.app + """ + match = INCLUDE_PATH_PATTERN.search(include_line) if not match: return None + include_path = match.group(1) parts = include_path.split("/") - return normalize_level("/".join(parts[:2])) + + if len(parts) >= 2: + include_level = f"{parts[0]}/{parts[1]}" + else: + include_level = include_path + + # If the "includelevel" indicates a file, cut off the filename + if "." in include_level.split("/")[-1]: + include_level = include_level.rsplit("/", 1)[0] + "/toplevel" + + return include_level.replace("/", ".") -def scan_file(path: Path, repo_root: Path) -> tuple[list[str], list[tuple[str, str]]]: - rel = path.relative_to(repo_root).as_posix() - src_level = source_level(rel) +def find_repository_directories(start_path, depth_limit=10): + """ + Find the repository root by looking for src or include folders. + Walks up the directory tree from the start path. + """ + current = start_path.resolve() - raw_lines: list[str] = [] - paths: list[tuple[str, str]] = [] + for _ in range(depth_limit): + src_path = current / "src" + include_path = current / "include" + has_src = src_path.exists() + has_include = include_path.exists() - with path.open("r", encoding="utf-8", errors="ignore") as handle: - for line in handle: - if "boost" in line: - continue - if not INCLUDE_PATTERN.match(line): - continue + if has_src or has_include: + dirs = [] + if has_src: + dirs.append(src_path) + if has_include: + dirs.append(include_path) + return current, dirs - line = line.rstrip("\n") - raw_lines.append(f"{rel}:{line}") + parent = current.parent + if parent == current: + break + current = parent - dst_level = include_level(line) - if dst_level is None: - continue - if src_level != dst_level: - paths.append((src_level, dst_level)) - - return raw_lines, paths + raise RuntimeError( + "Could not find repository root. " + "Expected to find a directory containing 'src' and/or 'include' folders." + ) -def iter_source_files(repo_root: Path) -> list[Path]: - files: list[Path] = [] - for top in ("include", "src"): - root = repo_root / top - if root.exists(): - files.extend(path for path in root.rglob("*") if path.is_file()) - files.sort(key=lambda p: p.relative_to(repo_root).as_posix()) - return files +def main(): + script_dir = Path(__file__).parent.resolve() + os.chdir(script_dir) + # Clean up and create results directory. + results_dir = script_dir / "results" + if results_dir.exists(): + import shutil -def write_relation_db( - results_dir: Path, - edge_counts: list[tuple[tuple[str, str], int]], -) -> tuple[dict[str, list[tuple[str, int]]], dict[str, list[tuple[str, int]]]]: + shutil.rmtree(results_dir) + results_dir.mkdir() + + # Find the repository root. + try: + repo_root, scan_dirs = find_repository_directories(script_dir) + print(f"Found repository root: {repo_root}") + for scan_dir in scan_dirs: + print(f" Scanning: {scan_dir.relative_to(repo_root)}") + except RuntimeError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + # Find all #include directives. + print("\nScanning for raw includes...") + raw_includes = [] + rawincludes_file = results_dir / "rawincludes.txt" + + with open(rawincludes_file, "w", buffering=8192) as raw_f: + for dir_path in scan_dirs: + for file_path in dir_path.rglob("*"): + if not file_path.is_file(): + continue + try: + rel_path_str = str(file_path.relative_to(repo_root)) + with open( + file_path, "r", encoding="utf-8", errors="ignore", buffering=8192 + ) as f: + for line in f: + if "#include" not in line or "boost" in line: + continue + if INCLUDE_PATTERN.match(line): + line_stripped = line.strip() + entry = f"{rel_path_str}:{line_stripped}\n" + print(entry, end="") + raw_f.write(entry) + raw_includes.append((rel_path_str, line_stripped)) + except Exception as e: + print(f"Error reading {file_path}: {e}", file=sys.stderr) + + # Build levelization paths and count directly. + print("Build levelization paths") + path_counts = defaultdict(int) + + for file_path, include_line in raw_includes: + include_level = extract_include_level(include_line) + if not include_level: + continue + level = get_level(file_path) + if level != include_level: + path_counts[(level, include_level)] += 1 + + # Sort and deduplicate paths. + print("Sort and deduplicate paths") + sorted_items = sorted( + path_counts.items(), + key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])), + ) + + paths_file = results_dir / "paths.txt" + with open(paths_file, "w") as f: + for (level, include_level), count in sorted_items: + line = f"{count:7} {level} {include_level}\n" + print(line.rstrip()) + f.write(line) + + # Split into flat-file database. + print("Split into flat-file database") includes_dir = results_dir / "includes" includedby_dir = results_dir / "includedby" - includes_dir.mkdir(parents=True, exist_ok=True) - includedby_dir.mkdir(parents=True, exist_ok=True) + includes_dir.mkdir() + includedby_dir.mkdir() - includes: dict[str, list[tuple[str, int]]] = defaultdict(list) - includedby: dict[str, list[tuple[str, int]]] = defaultdict(list) + includes_data = defaultdict(list) + includedby_data = defaultdict(list) - with (results_dir / "paths.txt").open("w", encoding="utf-8") as out: - for (src, dst), count in edge_counts: - out.write(f"{count:7d} {src} {dst}\n") - includes[src].append((dst, count)) - includedby[dst].append((src, count)) + for (level, include_level), count in sorted_items: + includes_data[level].append((include_level, count)) + includedby_data[include_level].append((level, count)) - for src, entries in includes.items(): - with (includes_dir / src).open("w", encoding="utf-8") as out: - for dst, count in entries: - out.write(f"{dst} {count}\n") + for level in sorted(includes_data.keys(), key=dictionary_sort_key): + with open(includes_dir / level, "w") as f: + for include_level, count in includes_data[level]: + line = f"{include_level} {count}\n" + print(line.rstrip()) + f.write(line) - for dst, entries in includedby.items(): - with (includedby_dir / dst).open("w", encoding="utf-8") as out: - for src, count in entries: - out.write(f"{src} {count}\n") + for include_level in sorted(includedby_data.keys(), key=dictionary_sort_key): + with open(includedby_dir / include_level, "w") as f: + for level, count in includedby_data[include_level]: + line = f"{level} {count}\n" + print(line.rstrip()) + f.write(line) - return includes, includedby + # Search for loops. + print("Search for loops") + loops_file = results_dir / "loops.txt" + ordering_file = results_dir / "ordering.txt" + # Pre-load all include files into memory for fast lookup. + includes_cache = {} + includes_lookup = {} -def build_loops_and_ordering( - includes: dict[str, list[tuple[str, int]]], -) -> tuple[list[str], list[str]]: - include_map = { - src: {dst: count for dst, count in entries} - for src, entries in includes.items() - } + for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name): + if not include_file.is_file(): + continue + includes_cache[include_file.name] = [] + includes_lookup[include_file.name] = {} + with open(include_file, "r") as f: + for line in f: + parts = line.strip().split() + if len(parts) >= 2: + name, count = parts[0], int(parts[1]) + includes_cache[include_file.name].append((name, count)) + includes_lookup[include_file.name][name] = count - ordering_lines: list[str] = [] - loops_lines: list[str] = [] + loops_found = set() - seen_pairs: set[tuple[str, str]] = set() + with open(loops_file, "w", buffering=8192) as loops_f, open( + ordering_file, "w", buffering=8192 + ) as ordering_f: + for source in sorted(includes_cache.keys()): + for include, include_freq in includes_cache[source]: + if include not in includes_lookup: + continue - for source in sorted(includes.keys()): - for include, includefreq in includes[source]: - if include not in include_map: - continue + source_freq = includes_lookup[include].get(source) - sourcefreq = include_map[include].get(source) - if sourcefreq is None: - ordering_lines.append(f"{source} > {include}\n") - continue + if source_freq is not None: + loop_key = tuple(sorted([source, include])) + if loop_key in loops_found: + continue + loops_found.add(loop_key) - if (include, source) in seen_pairs: - continue - seen_pairs.add((source, include)) + loops_f.write(f"Loop: {source} {include}\n") - loops_lines.append(f"Loop: {source} {include}\n") - if includefreq - sourcefreq > 3: - loops_lines.append(f" {source} > {include}\n\n") - elif sourcefreq - includefreq > 3: - loops_lines.append(f" {include} > {source}\n\n") - elif sourcefreq == includefreq: - loops_lines.append(f" {include} == {source}\n\n") - else: - loops_lines.append(f" {include} ~= {source}\n\n") + diff = include_freq - source_freq + if diff > 3: + loops_f.write(f" {source} > {include}\n\n") + elif diff < -3: + loops_f.write(f" {include} > {source}\n\n") + elif source_freq == include_freq: + loops_f.write(f" {include} == {source}\n\n") + else: + loops_f.write(f" {include} ~= {source}\n\n") + else: + ordering_f.write(f"{source} > {include}\n") - return ordering_lines, loops_lines + # Print results. + print("\nOrdering:") + with open(ordering_file, "r") as f: + print(f.read(), end="") - -def generate(results_dir: Path, repo_root: Path, workers: int) -> None: - if results_dir.exists(): - shutil.rmtree(results_dir) - results_dir.mkdir(parents=True) - - files = iter_source_files(repo_root) - - raw_by_file: dict[str, list[str]] = {} - paths_by_file: dict[str, list[tuple[str, str]]] = {} - - start = time.perf_counter() - if workers <= 1: - for file in files: - rel = file.relative_to(repo_root).as_posix() - raw, paths = scan_file(file, repo_root) - raw_by_file[rel] = raw - paths_by_file[rel] = paths - else: - with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool: - futures = { - file.relative_to(repo_root).as_posix(): pool.submit( - scan_file, file, repo_root - ) - for file in files - } - for rel in sorted(futures.keys()): - raw, paths = futures[rel].result() - raw_by_file[rel] = raw - paths_by_file[rel] = paths - - raw_lines: list[str] = [] - raw_lines.extend( - line - for rel in sorted(raw_by_file.keys()) - for line in raw_by_file[rel] - ) - with (results_dir / "rawincludes.txt").open("w", encoding="utf-8") as out: - out.write("\n".join(raw_lines)) - if raw_lines: - out.write("\n") - - path_pairs: list[tuple[str, str]] = [] - path_pairs.extend( - pair - for rel in sorted(paths_by_file.keys()) - for pair in paths_by_file[rel] - ) - counts = Counter(path_pairs) - - edge_counts = sorted( - counts.items(), - key=lambda item: ( - dictionary_sort_key(item[0][0]), - dictionary_sort_key(item[0][1]), - ), - ) - - includes, _ = write_relation_db(results_dir, edge_counts) - ordering, loops = build_loops_and_ordering(includes) - - with (results_dir / "ordering.txt").open("w", encoding="utf-8") as out: - out.writelines(ordering) - with (results_dir / "loops.txt").open("w", encoding="utf-8") as out: - out.writelines(loops) - - elapsed = time.perf_counter() - start - print( - f"levelization.py: scanned {len(files)} files, " - f"{len(raw_lines)} includes, {len(edge_counts)} unique paths in " - f"{elapsed:.2f}s" - ) - print((results_dir / "ordering.txt").read_text(encoding="utf-8"), end="") - print((results_dir / "loops.txt").read_text(encoding="utf-8"), end="") - - -def main() -> int: - script_dir = Path(__file__).resolve().parent - repo_root = script_dir.parents[1] - - parser = argparse.ArgumentParser() - parser.add_argument( - "--repo-root", - type=Path, - default=repo_root, - help="Repository root (defaults based on script location).", - ) - parser.add_argument( - "--results-dir", - type=Path, - default=script_dir / "results", - help="Output results directory.", - ) - parser.add_argument( - "--workers", - type=int, - default=min(32, (os.cpu_count() or 1)), - help="Thread count for source scanning (default: CPU count, max 32).", - ) - args = parser.parse_args() - - generated_dir = args.results_dir.resolve() - generate( - results_dir=generated_dir, - repo_root=args.repo_root.resolve(), - workers=max(1, args.workers), - ) - - return 0 + print("\nLoops:") + with open(loops_file, "r") as f: + print(f.read(), end="") if __name__ == "__main__": - raise SystemExit(main()) + main() From ea92477d2116d5c8d133e5e2fa599899f4403d6d Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 1 Apr 2026 14:12:33 +0900 Subject: [PATCH 14/18] Test: hint build_test_hooks.sh when hook wasm is empty in hso() --- src/test/jtx/impl/hook.cpp | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/test/jtx/impl/hook.cpp b/src/test/jtx/impl/hook.cpp index 8602e0053..07b72ad00 100644 --- a/src/test/jtx/impl/hook.cpp +++ b/src/test/jtx/impl/hook.cpp @@ -65,29 +65,16 @@ hso_delete(void (*f)(Json::Value& jv)) Json::Value hso(std::vector const& wasmBytes, void (*f)(Json::Value& jv)) { - if (wasmBytes.size() == 0) - throw std::runtime_error("empty hook wasm passed to hso()"); - - Json::Value jv; - jv[jss::CreateCode] = strHex(wasmBytes); - { - jv[jss::HookOn] = - "0000000000000000000000000000000000000000000000000000000000000000"; - jv[jss::HookNamespace] = to_string(uint256{beast::zero}); - jv[jss::HookApiVersion] = Json::Value{0}; - } - - if (f) - f(jv); - - return jv; + return hso(strHex(wasmBytes), f); } Json::Value hso(std::string const& wasmHex, void (*f)(Json::Value& jv)) { if (wasmHex.size() == 0) - throw std::runtime_error("empty hook wasm passed to hso()"); + throw std::runtime_error( + "empty hook wasm passed to hso(): run " + "src/test/app/build_test_hooks.sh to generate the hook wasm"); Json::Value jv; jv[jss::CreateCode] = wasmHex; From 73cf6d34cd5ba24aea8d0df95e012ef43e44354e Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 6 Mar 2026 20:58:07 +0900 Subject: [PATCH 15/18] Fix BEAST_ENHANCED_LOGGING not working and restore original behavior --- cmake/RippledCore.cmake | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmake/RippledCore.cmake b/cmake/RippledCore.cmake index c96c42b6c..f4b070e0f 100644 --- a/cmake/RippledCore.cmake +++ b/cmake/RippledCore.cmake @@ -68,6 +68,17 @@ target_link_libraries(xrpl.imports.main $<$:antithesis-sdk-cpp> ) +# date-tz for enhanced logging (always linked, code is #ifdef guarded) +if(TARGET date::date-tz) + target_link_libraries(xrpl.imports.main INTERFACE date::date-tz) +endif() + +# BEAST_ENHANCED_LOGGING: enable for Debug builds OR when explicitly requested +# Uses generator expression so it works with multi-config generators (Xcode, VS, Ninja Multi-Config) +target_compile_definitions(xrpl.imports.main INTERFACE + $<$,$>:BEAST_ENHANCED_LOGGING=1> +) + include(add_module) include(target_link_modules) From c461dd9055ba805e1e4e67b80eef5313833f78ac Mon Sep 17 00:00:00 2001 From: Alloy Networks <45832257+alloynetworks@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:38:34 +0530 Subject: [PATCH 16/18] change build instructions url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30efdb776..b34fa8394 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The server software that powers Xahau is called `xahaud` and is available in thi ### Build from Source -* [Read the build instructions in our documentation](https://xahau.network/infrastructure/building-xahau) +* [Read the build instructions in our documentation](https://xahau.network/docs/infrastructure/build-xahaud/) * If you encounter any issues, please [open an issue](https://github.com/xahau/xahaud/issues) ## Highlights of Xahau From f1f44ae232b40bf5516c9902393aa77d7784b1b1 Mon Sep 17 00:00:00 2001 From: tequ Date: Mon, 2 Feb 2026 16:39:22 +0900 Subject: [PATCH 17/18] Build xahau.h from source and check in workflow https://github.com/Xahau/xahaud/pull/674 --- .github/workflows/check-genesis-hooks.yml | 107 ++ hook/genesis/build_xahau_h.sh | 203 +++ hook/genesis/headers/error.h | 46 + hook/genesis/headers/extern.h | 352 +++++ hook/genesis/headers/hookapi.h | 50 + hook/genesis/headers/macro.h | 671 ++++++++++ hook/genesis/headers/sfcodes.h | 215 +++ hook/genesis/headers/types.h | 239 ++++ hook/genesis/makefile | 10 +- include/xrpl/hook/xahau.h | 1468 +++++++++++---------- 10 files changed, 2625 insertions(+), 736 deletions(-) create mode 100644 .github/workflows/check-genesis-hooks.yml create mode 100755 hook/genesis/build_xahau_h.sh create mode 100644 hook/genesis/headers/error.h create mode 100644 hook/genesis/headers/extern.h create mode 100644 hook/genesis/headers/hookapi.h create mode 100644 hook/genesis/headers/macro.h create mode 100644 hook/genesis/headers/sfcodes.h create mode 100644 hook/genesis/headers/types.h diff --git a/.github/workflows/check-genesis-hooks.yml b/.github/workflows/check-genesis-hooks.yml new file mode 100644 index 000000000..6dd58e7e3 --- /dev/null +++ b/.github/workflows/check-genesis-hooks.yml @@ -0,0 +1,107 @@ +name: Check Genesis Hooks + +on: + push: + pull_request: + +jobs: + check-genesis-hooks: + runs-on: ubuntu-24.04 + env: + CLANG_VERSION: 18 + name: Verify xahau.h is in sync with genesis hooks + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + # Install binaryen from GitHub Releases (pinned to version 100) + - name: Install binaryen (version 100) + run: | + curl -LO https://github.com/WebAssembly/binaryen/releases/download/version_100/binaryen-version_100-x86_64-linux.tar.gz + tar -xzf binaryen-version_100-x86_64-linux.tar.gz + sudo cp binaryen-version_100/bin/* /usr/local/bin/ + wasm-opt --version + + - name: Install clang-format + run: | + codename=$( lsb_release --codename --short ) + sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null <> $GITHUB_PATH + wasmcc -v || true + + # Build and install hook-cleaner tool + - name: Build and install hook-cleaner + run: | + git clone https://github.com/richardah/hook-cleaner-c.git /tmp/hook-cleaner + cd /tmp/hook-cleaner + make + cp hook-cleaner /usr/local/bin/ + chmod +x /usr/local/bin/hook-cleaner + + # Build and install guard_checker tool + - name: Build and install guard_checker + run: | + cd include/xrpl/hook + make + cp guard_checker /usr/local/bin/ + chmod +x /usr/local/bin/guard_checker + + # Verify all required tools are available + - name: Verify required tools + run: | + echo "Checking tool availability..." + command -v wasmcc || (echo "Error: wasmcc not found" && exit 1) + command -v wasm-opt || (echo "Error: wasm-opt not found" && exit 1) + command -v hook-cleaner || (echo "Error: hook-cleaner not found" && exit 1) + command -v guard_checker || (echo "Error: guard_checker not found" && exit 1) + command -v xxd || (echo "Error: xxd not found" && exit 1) + command -v clang-format || (echo "Error: clang-format not found" && exit 1) + echo "All tools verified successfully" + + # Execute build script to regenerate xahau.h + - name: Run build_xahau_h.sh + run: | + cd hook/genesis + ./build_xahau_h.sh + + # Check if xahau.h has changed (fail if out of sync) + - name: Verify xahau.h is in sync + run: | + if ! git diff --exit-code include/xrpl/hook/xahau.h; then + echo "" + echo "❌ ERROR: xahau.h is out of sync with genesis hooks" + echo "" + echo "The generated xahau.h differs from the committed version." + echo "Please run the following command and commit the changes:" + echo "" + echo " cd hook/genesis && ./build_xahau_h.sh" + echo "" + echo "Diff:" + git diff include/xrpl/hook/xahau.h + exit 1 + fi + echo "✅ xahau.h is in sync with genesis hooks" diff --git a/hook/genesis/build_xahau_h.sh b/hook/genesis/build_xahau_h.sh new file mode 100755 index 000000000..08e1547fe --- /dev/null +++ b/hook/genesis/build_xahau_h.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +# build_xahau_h.sh +# Builds genesis hook WASMs and updates xahau.h with hex arrays + +set -euo pipefail + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory and path constants +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +XAHAU_H="${SCRIPT_DIR}/../../include/xrpl/hook/xahau.h" +TEMP_DIR="${SCRIPT_DIR}/.temp" + +# Hook file mappings (space-separated: name:file) +HOOK_FILES=( + "GovernanceHook:govern.wasm" + "RewardHook:reward.wasm" + # "MintHook:mint.wasm" +) + +# Cleanup function +cleanup() { + local exit_code=$? + if [ ${exit_code} -eq 0 ] && [ -d "${TEMP_DIR}" ]; then + rm -rf "${TEMP_DIR}" + elif [ ${exit_code} -ne 0 ]; then + echo -e "${RED}Error: Script failed with exit code ${exit_code}${NC}" >&2 + if [ -d "${TEMP_DIR}" ]; then + echo -e "${YELLOW}Temp files preserved at: ${TEMP_DIR}${NC}" >&2 + fi + fi + exit ${exit_code} +} + +trap cleanup EXIT INT TERM + +# Tool verification +echo -e "${BLUE}==> Checking required tools...${NC}" +REQUIRED_TOOLS=("make" "xxd" "sed" "clang-format" "wasm-opt") +for tool in "${REQUIRED_TOOLS[@]}"; do + if ! command -v "${tool}" &> /dev/null; then + echo -e "${RED}Error: Required tool '${tool}' not found${NC}" >&2 + exit 1 + fi + echo -e "${GREEN} ✓ ${tool}${NC}" +done + +# Verify wasm-opt version is exactly 100 +WASM_OPT_VERSION=$(wasm-opt --version | grep -oE '[0-9]+' | head -1) +if [ "${WASM_OPT_VERSION}" != "100" ]; then + echo -e "${RED}Error: wasm-opt version must be 100, but found ${WASM_OPT_VERSION}${NC}" >&2 + exit 1 +fi +echo -e "${GREEN} ✓ wasm-opt version 100${NC}" + +# Verify xahau.h exists +if [ ! -f "${XAHAU_H}" ]; then + echo -e "${RED}Error: xahau.h not found at ${XAHAU_H}${NC}" >&2 + exit 1 +fi + +# Create temp directory +mkdir -p "${TEMP_DIR}" + +# Build all WASM files +echo -e "${BLUE}==> Building WASM files with 'make all'...${NC}" +cd "${SCRIPT_DIR}" +make all +echo -e "${GREEN} Build completed successfully${NC}" + +# Function to convert WASM to hex array +wasm_to_hex_array() { + local wasm_file="$1" + local indent=" " + + if [ ! -f "${wasm_file}" ]; then + echo -e "${RED}Error: WASM file not found: ${wasm_file}${NC}" >&2 + return 1 + fi + + # Convert to hex with xxd, format with sed + xxd -p -u -c 10 "${wasm_file}" | \ + sed 's/../0x&U,/g' | \ + sed "s/^/${indent}/g" | \ + sed '$ s/,$//' +} + +# Function to update hook array in xahau.h +update_hook_array() { + local hook_name="$1" + local hex_array="$2" + local temp_file="${TEMP_DIR}/xahau.h.tmp" + + echo -e "${BLUE}==> Updating ${hook_name}...${NC}" + + # Check if hook already exists + if grep -q "static const std::vector ${hook_name} = {" "${XAHAU_H}"; then + echo -e "${YELLOW} Replacing existing ${hook_name}${NC}" + + # Use awk to replace the array content + awk -v hook="${hook_name}" -v hex="${hex_array}" ' + BEGIN { in_array=0 } + { + if ($0 ~ "static const std::vector " hook " = {") { + print $0 + print hex + in_array=1 + next + } + if (in_array && $0 ~ /};/) { + print "};" + in_array=0 + next + } + if (!in_array) { + print $0 + } + } + ' "${XAHAU_H}" > "${temp_file}" + + mv "${temp_file}" "${XAHAU_H}" + else + echo -e "${YELLOW} Adding new ${hook_name}${NC}" + + # Find the position before #endif and add the new hook + awk -v hook="${hook_name}" -v hex="${hex_array}" ' + { + if ($0 ~ /#endif.*XAHAU_GENESIS_HOOKS/) { + print "" + print "static const std::vector " hook " = {" + print hex + print "};" + print "" + print $0 + } else { + print $0 + } + } + ' "${XAHAU_H}" > "${temp_file}" + + mv "${temp_file}" "${XAHAU_H}" + fi + + echo -e "${GREEN} ✓ ${hook_name} updated${NC}" +} + +# Process each hook +for hook_entry in "${HOOK_FILES[@]}"; do + hook_name="${hook_entry%%:*}" + wasm_file="${SCRIPT_DIR}/${hook_entry##*:}" + + echo -e "${BLUE}==> Converting ${wasm_file} to hex array...${NC}" + hex_array=$(wasm_to_hex_array "${wasm_file}") + + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Failed to convert ${wasm_file}${NC}" >&2 + exit 1 + fi + + echo -e "${GREEN} Conversion successful ($(echo "${hex_array}" | wc -l) lines)${NC}" + + update_hook_array "${hook_name}" "${hex_array}" +done + +# Format with clang-format +echo -e "${BLUE}==> Formatting with clang-format...${NC}" +cp "${XAHAU_H}" "${TEMP_DIR}/xahau.h.before_format" +clang-format -i "${XAHAU_H}" +echo -e "${GREEN} Formatting completed${NC}" + +# Verification +echo -e "${BLUE}==> Verifying changes...${NC}" +for hook_entry in "${HOOK_FILES[@]}"; do + hook_name="${hook_entry%%:*}" + if grep -q "static const std::vector ${hook_name} = {" "${XAHAU_H}"; then + echo -e "${GREEN} ✓ ${hook_name} found in xahau.h${NC}" + else + echo -e "${RED} ✗ ${hook_name} NOT found in xahau.h${NC}" >&2 + exit 1 + fi +done + +# Show summary +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Successfully updated xahau.h${NC}" +echo -e "${GREEN}========================================${NC}" +echo -e "Updated hooks:" +for hook_entry in "${HOOK_FILES[@]}"; do + hook_name="${hook_entry%%:*}" + wasm_file="${SCRIPT_DIR}/${hook_entry##*:}" + size=$(wc -c < "${wasm_file}" | tr -d ' ') + echo -e " - ${hook_name}: ${size} bytes" +done +echo "" +echo -e "File location: ${XAHAU_H}" +echo "" diff --git a/hook/genesis/headers/error.h b/hook/genesis/headers/error.h new file mode 100644 index 000000000..d423cfbbf --- /dev/null +++ b/hook/genesis/headers/error.h @@ -0,0 +1,46 @@ +// For documentation please see: https://xrpl-hooks.readme.io/reference/ +// Generated using generate_error.sh +#ifndef HOOK_ERROR_CODES +#define SUCCESS 0 +#define OUT_OF_BOUNDS -1 +#define INTERNAL_ERROR -2 +#define TOO_BIG -3 +#define TOO_SMALL -4 +#define DOESNT_EXIST -5 +#define NO_FREE_SLOTS -6 +#define INVALID_ARGUMENT -7 +#define ALREADY_SET -8 +#define PREREQUISITE_NOT_MET -9 +#define FEE_TOO_LARGE -10 +#define EMISSION_FAILURE -11 +#define TOO_MANY_NONCES -12 +#define TOO_MANY_EMITTED_TXN -13 +#define NOT_IMPLEMENTED -14 +#define INVALID_ACCOUNT -15 +#define GUARD_VIOLATION -16 +#define INVALID_FIELD -17 +#define PARSE_ERROR -18 +#define RC_ROLLBACK -19 +#define RC_ACCEPT -20 +#define NO_SUCH_KEYLET -21 +#define NOT_AN_ARRAY -22 +#define NOT_AN_OBJECT -23 +#define INVALID_FLOAT -10024 +#define DIVISION_BY_ZERO -25 +#define MANTISSA_OVERSIZED -26 +#define MANTISSA_UNDERSIZED -27 +#define EXPONENT_OVERSIZED -28 +#define EXPONENT_UNDERSIZED -29 +#define OVERFLOW -30 +#define NOT_IOU_AMOUNT -31 +#define NOT_AN_AMOUNT -32 +#define CANT_RETURN_NEGATIVE -33 +#define NOT_AUTHORIZED -34 +#define PREVIOUS_FAILURE_PREVENTS_RETRY -35 +#define TOO_MANY_PARAMS -36 +#define INVALID_TXN -37 +#define RESERVE_INSUFFICIENT -38 +#define COMPLEX_NOT_SUPPORTED -39 +#define DOES_NOT_MATCH -40 +#define HOOK_ERROR_CODES +#endif //HOOK_ERROR_CODES \ No newline at end of file diff --git a/hook/genesis/headers/extern.h b/hook/genesis/headers/extern.h new file mode 100644 index 000000000..5a7dc7711 --- /dev/null +++ b/hook/genesis/headers/extern.h @@ -0,0 +1,352 @@ +// For documentation please see: https://xrpl-hooks.readme.io/reference/ +// Generated using generate_extern.sh +#include +#ifndef HOOK_EXTERN + +extern int32_t __attribute__((noduplicate)) +_g(uint32_t guard_id, uint32_t maxiter); + +extern int64_t +accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); + +extern int64_t +emit( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); + +extern int64_t +etxn_burden(void); + +extern int64_t +etxn_details(uint32_t write_ptr, uint32_t write_len); + +extern int64_t +etxn_fee_base(uint32_t read_ptr, uint32_t read_len); + +extern int64_t +etxn_generation(void); + +extern int64_t +etxn_nonce(uint32_t write_ptr, uint32_t write_len); + +extern int64_t +etxn_reserve(uint32_t count); + +extern int64_t +fee_base(void); + +extern int64_t +float_compare(int64_t float1, int64_t float2, uint32_t mode); + +extern int64_t +float_divide(int64_t float1, int64_t float2); + +extern int64_t +float_exponent(int64_t float1); + +extern int64_t +float_exponent_set(int64_t float1, int32_t exponent); + +extern int64_t +float_int(int64_t float1, uint32_t decimal_places, uint32_t abs); + +extern int64_t +float_invert(int64_t float1); + +extern int64_t +float_log(int64_t float1); + +extern int64_t +float_mantissa(int64_t float1); + +extern int64_t +float_mantissa_set(int64_t float1, int64_t mantissa); + +extern int64_t +float_mulratio( + int64_t float1, + uint32_t round_up, + uint32_t numerator, + uint32_t denominator); + +extern int64_t +float_multiply(int64_t float1, int64_t float2); + +extern int64_t +float_negate(int64_t float1); + +extern int64_t +float_one(void); + +extern int64_t +float_root(int64_t float1, uint32_t n); + +extern int64_t +float_set(int32_t exponent, int64_t mantissa); + +extern int64_t +float_sign(int64_t float1); + +extern int64_t +float_sign_set(int64_t float1, uint32_t negative); + +extern int64_t +float_sto( + uint32_t write_ptr, + uint32_t write_len, + uint32_t cread_ptr, + uint32_t cread_len, + uint32_t iread_ptr, + uint32_t iread_len, + int64_t float1, + uint32_t field_code); + +extern int64_t +float_sto_set(uint32_t read_ptr, uint32_t read_len); + +extern int64_t +float_sum(int64_t float1, int64_t float2); + +extern int64_t +hook_account(uint32_t write_ptr, uint32_t write_len); + +extern int64_t +hook_again(void); + +extern int64_t +hook_hash(uint32_t write_ptr, uint32_t write_len, int32_t hook_no); + +extern int64_t +hook_param( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); + +extern int64_t +otxn_param( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); + +extern int64_t +hook_param_set( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t hread_ptr, + uint32_t hread_len); + +extern int64_t +hook_pos(void); + +extern int64_t +hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags); + +extern int64_t +ledger_keylet( + uint32_t write_ptr, + uint32_t write_len, + uint32_t lread_ptr, + uint32_t lread_len, + uint32_t hread_ptr, + uint32_t hread_len); + +extern int64_t +ledger_last_hash(uint32_t write_ptr, uint32_t write_len); + +extern int64_t +ledger_last_time(void); + +extern int64_t +ledger_nonce(uint32_t write_ptr, uint32_t write_len); + +extern int64_t +ledger_seq(void); + +extern int64_t +meta_slot(uint32_t slot_no); + +extern int64_t +otxn_burden(void); + +extern int64_t +otxn_field(uint32_t write_ptr, uint32_t write_len, uint32_t field_id); + +extern int64_t +otxn_field_txt(uint32_t write_ptr, uint32_t write_len, uint32_t field_id); + +extern int64_t +otxn_generation(void); + +extern int64_t +otxn_id(uint32_t write_ptr, uint32_t write_len, uint32_t flags); + +extern int64_t +otxn_slot(uint32_t slot_no); + +extern int64_t +otxn_type(void); + +extern int64_t +rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); + +extern int64_t +slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot); + +extern int64_t +slot_clear(uint32_t slot); + +extern int64_t +slot_count(uint32_t slot); + +extern int64_t +slot_float(uint32_t slot_no); + +extern int64_t +slot_id(uint32_t write_ptr, uint32_t write_len, uint32_t slot); + +extern int64_t +slot_set(uint32_t read_ptr, uint32_t read_len, uint32_t slot); + +extern int64_t +slot_size(uint32_t slot); + +extern int64_t +slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot); + +extern int64_t +slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot); + +extern int64_t +slot_type(uint32_t slot_no, uint32_t flags); + +extern int64_t +state( + uint32_t write_ptr, + uint32_t write_len, + uint32_t kread_ptr, + uint32_t kread_len); + +extern int64_t +state_foreign( + uint32_t write_ptr, + uint32_t write_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t nread_ptr, + uint32_t nread_len, + uint32_t aread_ptr, + uint32_t aread_len); + +extern int64_t +state_foreign_set( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t nread_ptr, + uint32_t nread_len, + uint32_t aread_ptr, + uint32_t aread_len); + +extern int64_t +state_set( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len); + +extern int64_t +sto_emplace( + uint32_t write_ptr, + uint32_t write_len, + uint32_t sread_ptr, + uint32_t sread_len, + uint32_t fread_ptr, + uint32_t fread_len, + uint32_t field_id); + +extern int64_t +sto_erase( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len, + uint32_t field_id); + +extern int64_t +sto_subarray(uint32_t read_ptr, uint32_t read_len, uint32_t array_id); + +extern int64_t +sto_subfield(uint32_t read_ptr, uint32_t read_len, uint32_t field_id); + +extern int64_t +sto_validate(uint32_t tread_ptr, uint32_t tread_len); + +extern int64_t +trace( + uint32_t mread_ptr, + uint32_t mread_len, + uint32_t dread_ptr, + uint32_t dread_len, + uint32_t as_hex); + +extern int64_t +trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1); + +extern int64_t +trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number); + +extern int64_t +trace_slot(uint32_t read_ptr, uint32_t read_len, uint32_t slot); + +extern int64_t +util_accid( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); + +extern int64_t +util_keylet( + uint32_t write_ptr, + uint32_t write_len, + uint32_t keylet_type, + uint32_t a, + uint32_t b, + uint32_t c, + uint32_t d, + uint32_t e, + uint32_t f); + +extern int64_t +util_raddr( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); + +extern int64_t +util_sha512h( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); + +extern int64_t +util_verify( + uint32_t dread_ptr, + uint32_t dread_len, + uint32_t sread_ptr, + uint32_t sread_len, + uint32_t kread_ptr, + uint32_t kread_len); + +extern int64_t xpop_slot(uint32_t, uint32_t); +#define HOOK_EXTERN +#endif // HOOK_EXTERN \ No newline at end of file diff --git a/hook/genesis/headers/hookapi.h b/hook/genesis/headers/hookapi.h new file mode 100644 index 000000000..c42ebe655 --- /dev/null +++ b/hook/genesis/headers/hookapi.h @@ -0,0 +1,50 @@ +/** + * Hook API include file + * + * Note to the reader: + * This include defines two types of things: external functions and macros + * Functions are used sparingly because a non-inlining compiler may produce + * undesirable output. + * + * Find documentation here: https://xrpl-hooks.readme.io/reference/ + */ + +#ifndef HOOKAPI_INCLUDED +#define HOOKAPI_INCLUDED 1 + +#define KEYLET_HOOK 1 +#define KEYLET_HOOK_STATE 2 +#define KEYLET_ACCOUNT 3 +#define KEYLET_AMENDMENTS 4 +#define KEYLET_CHILD 5 +#define KEYLET_SKIP 6 +#define KEYLET_FEES 7 +#define KEYLET_NEGATIVE_UNL 8 +#define KEYLET_LINE 9 +#define KEYLET_OFFER 10 +#define KEYLET_QUALITY 11 +#define KEYLET_EMITTED_DIR 12 +#define KEYLET_TICKET 13 +#define KEYLET_SIGNERS 14 +#define KEYLET_CHECK 15 +#define KEYLET_DEPOSIT_PREAUTH 16 +#define KEYLET_UNCHECKED 17 +#define KEYLET_OWNER_DIR 18 +#define KEYLET_PAGE 19 +#define KEYLET_ESCROW 20 +#define KEYLET_PAYCHAN 21 +#define KEYLET_EMITTED 22 +#define KEYLET_NFT_OFFER 23 +#define KEYLET_HOOK_DEFINITION 24 + +#define COMPARE_EQUAL 1U +#define COMPARE_LESS 2U +#define COMPARE_GREATER 4U + +#include "error.h" +#include "extern.h" +#include "sfcodes.h" +#include "macro.h" +#include "types.h" + +#endif \ No newline at end of file diff --git a/hook/genesis/headers/macro.h b/hook/genesis/headers/macro.h new file mode 100644 index 000000000..a02dfb81a --- /dev/null +++ b/hook/genesis/headers/macro.h @@ -0,0 +1,671 @@ +/** + * These are helper macros for writing hooks, all of them are optional as is including hookmacro.h at all + */ + +#include +#include "hookapi.h" +#include "sfcodes.h" + +#ifndef HOOKMACROS_INCLUDED +#define HOOKMACROS_INCLUDED 1 + + +#ifdef NDEBUG +#define DEBUG 0 +#else +#define DEBUG 1 +#endif + +#define TRACEVAR(v) if (DEBUG) trace_num((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v); +#define TRACEHEX(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), (uint32_t)(sizeof(v)), 1); +#define TRACEXFL(v) if (DEBUG) trace_float((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v); +#define TRACESTR(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), sizeof(v), 0); + +// hook developers should use this guard macro, simply GUARD() +#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) +#define GUARDM(maxiter, n) _g(( (1ULL << 31U) + (__LINE__ << 16) + n), (maxiter)+1) + +#define SBUF(str) (uint32_t)(str), sizeof(str) + +#define REQUIRE(cond, str)\ +{\ + if (!(cond))\ + rollback(SBUF(str), __LINE__);\ +} + +// make a report buffer as a c-string +// provide a name for a buffer to declare (buf) +// provide a static string +// provide an integer to print after the string +#define RBUF(buf, out_len, str, num)\ +unsigned char buf[sizeof(str) + 21];\ +int out_len = 0;\ +{\ + int i = 0;\ + for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\ + (buf)[i] = str[i];\ + if ((buf)[sizeof(str)-1] == 0) i--;\ + if ((num) < 0) (buf)[i++] = '-';\ + uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\ + uint64_t j = 10000000000000000000ULL;\ + int start = 1;\ + for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\ + {\ + unsigned char digit = ( unsigned_num / j ) % 10;\ + if (digit == 0 && start)\ + continue;\ + start = 0;\ + (buf)[i++] = '0' + digit;\ + }\ + (buf)[i] = '\0';\ + out_len = i;\ +} + +#define RBUF2(buff, out_len, str, num, str2, num2)\ +unsigned char buff[sizeof(str) + sizeof(str2) + 42];\ +int out_len = 0;\ +{\ + unsigned char* buf = buff;\ + int i = 0;\ + for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\ + (buf)[i] = str[i];\ + if ((buf)[sizeof(str)-1] == 0) i--;\ + if ((num) < 0) (buf)[i++] = '-';\ + uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\ + uint64_t j = 10000000000000000000ULL;\ + int start = 1;\ + for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\ + {\ + unsigned char digit = ( unsigned_num / j ) % 10;\ + if (digit == 0 && start)\ + continue;\ + start = 0;\ + (buf)[i++] = '0' + digit;\ + }\ + buf += i;\ + out_len += i;\ + i = 0;\ + for (; GUARDM(sizeof(str2),3),i < sizeof(str2); ++i)\ + (buf)[i] = str2[i];\ + if ((buf)[sizeof(str2)-1] == 0) i--;\ + if ((num2) < 0) (buf)[i++] = '-';\ + unsigned_num = (uint64_t)( (num2) < 0 ? (num2) * -1 : (num2) );\ + j = 10000000000000000000ULL;\ + start = 1;\ + for (; GUARDM(20,4), unsigned_num > 0 && j > 0; j /= 10)\ + {\ + unsigned char digit = ( unsigned_num / j ) % 10;\ + if (digit == 0 && start)\ + continue;\ + start = 0;\ + (buf)[i++] = '0' + digit;\ + }\ + (buf)[i] = '\0';\ + out_len += i;\ +} + +#define CLEARBUF(b)\ +{\ + for (int x = 0; GUARD(sizeof(b)), x < sizeof(b); ++x)\ + b[x] = 0;\ +} + +// returns an in64_t, negative if error, non-negative if valid drops +#define AMOUNT_TO_DROPS(amount_buffer)\ + (((amount_buffer)[0] >> 7) ? -2 : (\ + ((((uint64_t)((amount_buffer)[0])) & 0xb00111111) << 56) +\ + (((uint64_t)((amount_buffer)[1])) << 48) +\ + (((uint64_t)((amount_buffer)[2])) << 40) +\ + (((uint64_t)((amount_buffer)[3])) << 32) +\ + (((uint64_t)((amount_buffer)[4])) << 24) +\ + (((uint64_t)((amount_buffer)[5])) << 16) +\ + (((uint64_t)((amount_buffer)[6])) << 8) +\ + (((uint64_t)((amount_buffer)[7]))))) + +#define SUB_OFFSET(x) ((int32_t)(x >> 32)) +#define SUB_LENGTH(x) ((int32_t)(x & 0xFFFFFFFFULL)) + +#define BUFFER_EQUAL_20(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4)) + +#define BUFFER_EQUAL_32(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\ + *(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3)) + + +// when using this macro buf1len may be dynamic but buf2len must be static +// provide n >= 1 to indicate how many times the macro will be hit on the line of code +// e.g. if it is in a loop that loops 10 times n = 10 + +#define BUFFER_EQUAL_GUARD(output, buf1, buf1len, buf2, buf2len, n)\ +{\ + output = ((buf1len) == (buf2len) ? 1 : 0);\ + for (int x = 0; GUARDM( (buf2len) * (n), 1 ), output && x < (buf2len);\ + ++x)\ + output = *(((uint8_t*)(buf1)) + x) == *(((uint8_t*)(buf2)) + x);\ +} + +#define BUFFER_SWAP(x,y)\ +{\ + uint8_t* z = x;\ + x = y;\ + y = z;\ +} + +#define ACCOUNT_COMPARE(compare_result, buf1, buf2)\ +{\ + compare_result = 0;\ + for (int i = 0; GUARD(20), i < 20; ++i)\ + {\ + if (buf1[i] > buf2[i])\ + {\ + compare_result = 1;\ + break;\ + }\ + else if (buf1[i] < buf2[i])\ + {\ + compare_result = -1;\ + break;\ + }\ + }\ +} + +#define BUFFER_EQUAL_STR_GUARD(output, buf1, buf1len, str, n)\ + BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), n) + +#define BUFFER_EQUAL_STR(output, buf1, buf1len, str)\ + BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), 1) + +#define BUFFER_EQUAL(output, buf1, buf2, compare_len)\ + BUFFER_EQUAL_GUARD(output, buf1, compare_len, buf2, compare_len, 1) + +#define UINT16_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[1] = (((uint64_t)i) >> 0) & 0xFFUL;\ +} + +#define UINT16_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 8) +\ + ((uint64_t)((buf)[1]) << 0)) + +#define UINT32_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 24) & 0xFFUL;\ + buf[1] = (((uint64_t)i) >> 16) & 0xFFUL;\ + buf[2] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[3] = (((uint64_t)i) >> 0) & 0xFFUL;\ +} + + +#define UINT32_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 24) +\ + ((uint64_t)((buf)[1]) << 16) +\ + ((uint64_t)((buf)[2]) << 8) +\ + ((uint64_t)((buf)[3]) << 0)) + +#define UINT64_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 56) & 0xFFUL;\ + buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\ + buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\ + buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\ + buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\ + buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\ + buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\ +} + + +#define UINT64_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 56) +\ + ((uint64_t)((buf)[1]) << 48) +\ + ((uint64_t)((buf)[2]) << 40) +\ + ((uint64_t)((buf)[3]) << 32) +\ + ((uint64_t)((buf)[4]) << 24) +\ + ((uint64_t)((buf)[5]) << 16) +\ + ((uint64_t)((buf)[6]) << 8) +\ + ((uint64_t)((buf)[7]) << 0)) + + +#define INT64_FROM_BUF(buf)\ + ((((uint64_t)((buf)[0] & 0x7FU) << 56) +\ + ((uint64_t)((buf)[1]) << 48) +\ + ((uint64_t)((buf)[2]) << 40) +\ + ((uint64_t)((buf)[3]) << 32) +\ + ((uint64_t)((buf)[4]) << 24) +\ + ((uint64_t)((buf)[5]) << 16) +\ + ((uint64_t)((buf)[6]) << 8) +\ + ((uint64_t)((buf)[7]) << 0)) * (buf[0] & 0x80U ? -1 : 1)) + +#define INT64_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 56) & 0x7FUL;\ + buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\ + buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\ + buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\ + buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\ + buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\ + buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\ + if (i < 0) buf[0] |= 0x80U;\ +} + +#define ttPAYMENT 0 +#define ttESCROW_CREATE 1 +#define ttESCROW_FINISH 2 +#define ttACCOUNT_SET 3 +#define ttESCROW_CANCEL 4 +#define ttREGULAR_KEY_SET 5 +#define ttOFFER_CREATE 7 +#define ttOFFER_CANCEL 8 +#define ttTICKET_CREATE 10 +#define ttSIGNER_LIST_SET 12 +#define ttPAYCHAN_CREATE 13 +#define ttPAYCHAN_FUND 14 +#define ttPAYCHAN_CLAIM 15 +#define ttCHECK_CREATE 16 +#define ttCHECK_CASH 17 +#define ttCHECK_CANCEL 18 +#define ttDEPOSIT_PREAUTH 19 +#define ttTRUST_SET 20 +#define ttACCOUNT_DELETE 21 +#define ttHOOK_SET 22 +#define ttNFTOKEN_MINT 25 +#define ttNFTOKEN_BURN 26 +#define ttNFTOKEN_CREATE_OFFER 27 +#define ttNFTOKEN_CANCEL_OFFER 28 +#define ttNFTOKEN_ACCEPT_OFFER 29 +#define ttURITOKEN_MINT 45 +#define ttURITOKEN_BURN 46 +#define ttURITOKEN_BUY 47 +#define ttURITOKEN_CREATE_SELL_OFFER 48 +#define ttURITOKEN_CANCEL_SELL_OFFER 49 +#define ttCLAIM_REWARD 98 +#define ttINVOKE 99 +#define ttAMENDMENT 100 +#define ttFEE 101 +#define ttUNL_MODIFY 102 +#define ttEMIT_FAILURE 103 +#define tfCANONICAL 0x80000000UL + +#define atACCOUNT 1U +#define atOWNER 2U +#define atDESTINATION 3U +#define atISSUER 4U +#define atAUTHORIZE 5U +#define atUNAUTHORIZE 6U +#define atTARGET 7U +#define atREGULARKEY 8U +#define atPSEUDOCALLBACK 9U + +#define amAMOUNT 1U +#define amBALANCE 2U +#define amLIMITAMOUNT 3U +#define amTAKERPAYS 4U +#define amTAKERGETS 5U +#define amLOWLIMIT 6U +#define amHIGHLIMIT 7U +#define amFEE 8U +#define amSENDMAX 9U +#define amDELIVERMIN 10U +#define amMINIMUMOFFER 16U +#define amRIPPLEESCROW 17U +#define amDELIVEREDAMOUNT 18U + +/** + * RH NOTE -- PAY ATTENTION + * + * ALL 'ENCODE' MACROS INCREMENT BUF_OUT + * THIS IS TO MAKE CHAINING EASY + * BUF_OUT IS A SACRIFICIAL POINTER + * + * 'ENCODE' MACROS WITH CONSTANTS HAVE + * ALIASING TO ASSIST YOU WITH ORDER + * _TYPECODE_FIELDCODE_ENCODE_MACRO + * TO PRODUCE A SERIALIZED OBJECT + * IN CANONICAL FORMAT YOU MUST ORDER + * FIRST BY TYPE CODE THEN BY FIELD CODE + * + * ALL 'PREPARE' MACROS PRESERVE POINTERS + * + **/ + + +#define ENCODE_TL_SIZE 49 +#define ENCODE_TL(buf_out, tlamt, amount_type)\ +{\ + uint8_t uat = amount_type; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + for (int i = 1; GUARDM(48, 1), i < 49; ++i)\ + buf_out[i] = tlamt[i-1];\ + buf_out += ENCODE_TL_SIZE;\ +} +#define _06_XX_ENCODE_TL(buf_out, drops, amount_type )\ + ENCODE_TL(buf_out, drops, amount_type ); +#define ENCODE_TL_AMOUNT(buf_out, drops )\ + ENCODE_TL(buf_out, drops, amAMOUNT ); +#define _06_01_ENCODE_TL_AMOUNT(buf_out, drops )\ + ENCODE_TL_AMOUNT(buf_out, drops ); + + +// Encode drops to serialization format +// consumes 9 bytes +#define ENCODE_DROPS_SIZE 9 +#define ENCODE_DROPS(buf_out, drops, amount_type ) \ + {\ + uint8_t uat = amount_type; \ + uint64_t udrops = drops; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \ + buf_out[2] = (udrops >> 48) & 0xFFU; \ + buf_out[3] = (udrops >> 40) & 0xFFU; \ + buf_out[4] = (udrops >> 32) & 0xFFU; \ + buf_out[5] = (udrops >> 24) & 0xFFU; \ + buf_out[6] = (udrops >> 16) & 0xFFU; \ + buf_out[7] = (udrops >> 8) & 0xFFU; \ + buf_out[8] = (udrops >> 0) & 0xFFU; \ + buf_out += ENCODE_DROPS_SIZE; \ + } + +#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\ + ENCODE_DROPS(buf_out, drops, amount_type ); + +#define ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amAMOUNT ); +#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS_AMOUNT(buf_out, drops ); + +#define ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amFEE ); +#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS_FEE(buf_out, drops ); + +#define ENCODE_TT_SIZE 3 +#define ENCODE_TT(buf_out, tt )\ + {\ + uint8_t utt = tt;\ + buf_out[0] = 0x12U;\ + buf_out[1] =(utt >> 8 ) & 0xFFU;\ + buf_out[2] =(utt >> 0 ) & 0xFFU;\ + buf_out += ENCODE_TT_SIZE; \ + } +#define _01_02_ENCODE_TT(buf_out, tt)\ + ENCODE_TT(buf_out, tt); + + +#define ENCODE_ACCOUNT_SIZE 22 +#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + {\ + uint8_t uat = account_type;\ + buf_out[0] = 0x80U + uat;\ + buf_out[1] = 0x14U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\ + *(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\ + buf_out += ENCODE_ACCOUNT_SIZE;\ + } +#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + ENCODE_ACCOUNT(buf_out, account_id, account_type); + +#define ENCODE_ACCOUNT_SRC_SIZE 22 +#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT); +#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT_SRC(buf_out, account_id); + +#define ENCODE_ACCOUNT_DST_SIZE 22 +#define ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION); +#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT_DST(buf_out, account_id); + +#define ENCODE_ACCOUNT_OWNER_SIZE 22 +#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT(buf_out, account_id, atOWNER); +#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT_OWNER(buf_out, account_id); + +#define ENCODE_UINT32_COMMON_SIZE 5U +#define ENCODE_UINT32_COMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U +(uf & 0x0FU); \ + buf_out[1] =(ui >> 24 ) & 0xFFU; \ + buf_out[2] =(ui >> 16 ) & 0xFFU; \ + buf_out[3] =(ui >> 8 ) & 0xFFU; \ + buf_out[4] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_COMMON_SIZE; \ + } +#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\ + ENCODE_UINT32_COMMON(buf_out, i, field)\ + +#define ENCODE_UINT32_UNCOMMON_SIZE 6U +#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U; \ + buf_out[1] = uf; \ + buf_out[2] =(ui >> 24 ) & 0xFFU; \ + buf_out[3] =(ui >> 16 ) & 0xFFU; \ + buf_out[4] =(ui >> 8 ) & 0xFFU; \ + buf_out[5] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \ + } +#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + +#define ENCODE_LLS_SIZE 6U +#define ENCODE_LLS(buf_out, lls )\ + ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B ); +#define _02_27_ENCODE_LLS(buf_out, lls )\ + ENCODE_LLS(buf_out, lls ); + +#define ENCODE_FLS_SIZE 6U +#define ENCODE_FLS(buf_out, fls )\ + ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A ); +#define _02_26_ENCODE_FLS(buf_out, fls )\ + ENCODE_FLS(buf_out, fls ); + +#define ENCODE_TAG_SRC_SIZE 5 +#define ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x3U ); +#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_TAG_SRC(buf_out, tag ); + +#define ENCODE_TAG_DST_SIZE 5 +#define ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0xEU ); +#define _02_14_ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_TAG_DST(buf_out, tag ); + +#define ENCODE_SEQUENCE_SIZE 5 +#define ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U ); +#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_SEQUENCE(buf_out, sequence ); + +#define ENCODE_FLAGS_SIZE 5 +#define ENCODE_FLAGS(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x2U ); +#define _02_02_ENCODE_FLAGS(buf_out, tag )\ + ENCODE_FLAGS(buf_out, tag ); + +#define ENCODE_SIGNING_PUBKEY_SIZE 35 +#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\ + *(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\ + *(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\ + buf[34] = pkey[32];\ + buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\ + } + +#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + ENCODE_SIGNING_PUBKEY(buf_out, pkey ); + +#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 2 +#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + {\ + *buf_out++ = 0x73U;\ + *buf_out++ = 0x00U;\ + } + +#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + ENCODE_SIGNING_PUBKEY_NULL(buf_out ); + + +#define _0E_0E_ENCODE_HOOKOBJ(buf_out, hhash)\ +{\ + uint8_t* hook0 = (hhash);\ + *buf_out++ = 0xEEU; /* hook obj start */ \ + if (hook0 == 0) /* noop */\ + {\ + /* do nothing */ \ + }\ + else\ + {\ + *buf_out++ = 0x22U; /* flags = override */\ + *buf_out++ = 0x00U;\ + *buf_out++ = 0x00U;\ + *buf_out++ = 0x00U;\ + *buf_out++ = 0x01U;\ + if (hook0 == 0xFFFFFFFFUL) /* delete operation */ \ + {\ + *buf_out++ = 0x7BU; /* empty createcode */ \ + *buf_out++ = 0x00U;\ + }\ + else\ + {\ + *buf_out++ = 0x50U; /* HookHash */\ + *buf_out++ = 0x1FU;\ + uint64_t* d = (uint64_t*)buf_out;\ + uint64_t* s = (uint64_t*)hook0;\ + *d++ = *s++;\ + *d++ = *s++;\ + *d++ = *s++;\ + *d++ = *s++;\ + buf_out+=32;\ + }\ + }\ + *buf_out++ = 0xE1U;\ +} + +#define PREPARE_HOOKSET(buf_out_master, maxlen, h, sizeout)\ + {\ + uint8_t* buf_out = (buf_out_master); \ + uint8_t acc[20]; \ + uint32_t cls = (uint32_t)ledger_seq(); \ + hook_account(SBUF(acc)); \ + _01_02_ENCODE_TT (buf_out, ttHOOK_SET ); \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); \ + _02_04_ENCODE_SEQUENCE (buf_out, 0 ); \ + _02_26_ENCODE_FLS (buf_out, cls + 1 ); \ + _02_27_ENCODE_LLS (buf_out, cls + 5 ); \ + uint8_t* fee_ptr = buf_out; \ + _06_08_ENCODE_DROPS_FEE (buf_out, 0 ); \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); \ + uint32_t remaining_size = (maxlen) - (buf_out - (buf_out_master)); \ + int64_t edlen = etxn_details((uint32_t)buf_out, remaining_size); \ + buf_out += edlen; \ + *buf_out++ = 0xFBU; /* hook array start */ \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[0]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[1]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[2]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[3]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[4]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[5]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[6]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[7]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[8]); \ + _0E_0E_ENCODE_HOOKOBJ (buf_out, h[9]); \ + *buf_out++ = 0xF1U; /* hook array end */ \ + sizeout = (buf_out - (buf_out_master)); \ + int64_t fee = etxn_fee_base(buf_out_master, sizeout); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + +#ifdef HAS_CALLBACK +#define PREPARE_PAYMENT_SIMPLE_SIZE 270U +#else +#define PREPARE_PAYMENT_SIMPLE_SIZE 248U +#endif + +#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = buf_out_master;\ + uint8_t acc[20];\ + uint64_t drops_amount = (drops_amount_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + uint32_t cls = (uint32_t)ledger_seq();\ + hook_account(SBUF(acc));\ + _01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \ + _02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \ + _02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \ + _02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \ + _06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \ + uint8_t* fee_ptr = buf_out;\ + _06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \ + int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \ + int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + +#ifdef HAS_CALLBACK +#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 309 +#else +#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 287 +#endif +#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE(buf_out_master, tlamt, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = buf_out_master;\ + uint8_t acc[20];\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + uint32_t cls = (uint32_t)ledger_seq();\ + hook_account(SBUF(acc));\ + _01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \ + _02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \ + _02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \ + _02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \ + _06_01_ENCODE_TL_AMOUNT (buf_out, tlamt ); /* amount | size 48 */ \ + uint8_t* fee_ptr = buf_out;\ + _06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \ + etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); /* emitdet | size 1?? */ \ + int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + + + +#endif + diff --git a/hook/genesis/headers/sfcodes.h b/hook/genesis/headers/sfcodes.h new file mode 100644 index 000000000..17f2230ad --- /dev/null +++ b/hook/genesis/headers/sfcodes.h @@ -0,0 +1,215 @@ +// For documentation please see: https://xrpl-hooks.readme.io/reference/ +// Generated using generate_sfcodes.sh +#define sfCloseResolution ((16U << 16U) + 1U) +#define sfMethod ((16U << 16U) + 2U) +#define sfTransactionResult ((16U << 16U) + 3U) +#define sfTickSize ((16U << 16U) + 16U) +#define sfUNLModifyDisabling ((16U << 16U) + 17U) +#define sfHookResult ((16U << 16U) + 18U) +#define sfLedgerEntryType ((1U << 16U) + 1U) +#define sfTransactionType ((1U << 16U) + 2U) +#define sfSignerWeight ((1U << 16U) + 3U) +#define sfTransferFee ((1U << 16U) + 4U) +#define sfVersion ((1U << 16U) + 16U) +#define sfHookStateChangeCount ((1U << 16U) + 17U) +#define sfHookEmitCount ((1U << 16U) + 18U) +#define sfHookExecutionIndex ((1U << 16U) + 19U) +#define sfHookApiVersion ((1U << 16U) + 20U) +#define sfNetworkID ((2U << 16U) + 1U) +#define sfFlags ((2U << 16U) + 2U) +#define sfSourceTag ((2U << 16U) + 3U) +#define sfSequence ((2U << 16U) + 4U) +#define sfPreviousTxnLgrSeq ((2U << 16U) + 5U) +#define sfLedgerSequence ((2U << 16U) + 6U) +#define sfCloseTime ((2U << 16U) + 7U) +#define sfParentCloseTime ((2U << 16U) + 8U) +#define sfSigningTime ((2U << 16U) + 9U) +#define sfExpiration ((2U << 16U) + 10U) +#define sfTransferRate ((2U << 16U) + 11U) +#define sfWalletSize ((2U << 16U) + 12U) +#define sfOwnerCount ((2U << 16U) + 13U) +#define sfDestinationTag ((2U << 16U) + 14U) +#define sfHighQualityIn ((2U << 16U) + 16U) +#define sfHighQualityOut ((2U << 16U) + 17U) +#define sfLowQualityIn ((2U << 16U) + 18U) +#define sfLowQualityOut ((2U << 16U) + 19U) +#define sfQualityIn ((2U << 16U) + 20U) +#define sfQualityOut ((2U << 16U) + 21U) +#define sfStampEscrow ((2U << 16U) + 22U) +#define sfBondAmount ((2U << 16U) + 23U) +#define sfLoadFee ((2U << 16U) + 24U) +#define sfOfferSequence ((2U << 16U) + 25U) +#define sfFirstLedgerSequence ((2U << 16U) + 26U) +#define sfLastLedgerSequence ((2U << 16U) + 27U) +#define sfTransactionIndex ((2U << 16U) + 28U) +#define sfOperationLimit ((2U << 16U) + 29U) +#define sfReferenceFeeUnits ((2U << 16U) + 30U) +#define sfReserveBase ((2U << 16U) + 31U) +#define sfReserveIncrement ((2U << 16U) + 32U) +#define sfSetFlag ((2U << 16U) + 33U) +#define sfClearFlag ((2U << 16U) + 34U) +#define sfSignerQuorum ((2U << 16U) + 35U) +#define sfCancelAfter ((2U << 16U) + 36U) +#define sfFinishAfter ((2U << 16U) + 37U) +#define sfSignerListID ((2U << 16U) + 38U) +#define sfSettleDelay ((2U << 16U) + 39U) +#define sfTicketCount ((2U << 16U) + 40U) +#define sfTicketSequence ((2U << 16U) + 41U) +#define sfNFTokenTaxon ((2U << 16U) + 42U) +#define sfMintedNFTokens ((2U << 16U) + 43U) +#define sfBurnedNFTokens ((2U << 16U) + 44U) +#define sfHookStateCount ((2U << 16U) + 45U) +#define sfEmitGeneration ((2U << 16U) + 46U) +#define sfLockCount ((2U << 16U) + 47U) +#define sfRewardTime ((2U << 16U) + 98U) +#define sfRewardLgrFirst ((2U << 16U) + 99U) +#define sfRewardLgrLast ((2U << 16U) + 100U) +#define sfIndexNext ((3U << 16U) + 1U) +#define sfIndexPrevious ((3U << 16U) + 2U) +#define sfBookNode ((3U << 16U) + 3U) +#define sfOwnerNode ((3U << 16U) + 4U) +#define sfBaseFee ((3U << 16U) + 5U) +#define sfExchangeRate ((3U << 16U) + 6U) +#define sfLowNode ((3U << 16U) + 7U) +#define sfHighNode ((3U << 16U) + 8U) +#define sfDestinationNode ((3U << 16U) + 9U) +#define sfCookie ((3U << 16U) + 10U) +#define sfServerVersion ((3U << 16U) + 11U) +#define sfNFTokenOfferNode ((3U << 16U) + 12U) +#define sfEmitBurden ((3U << 16U) + 13U) +#define sfHookInstructionCount ((3U << 16U) + 17U) +#define sfHookReturnCode ((3U << 16U) + 18U) +#define sfReferenceCount ((3U << 16U) + 19U) +#define sfRewardAccumulator ((3U << 16U) + 100U) +#define sfEmailHash ((4U << 16U) + 1U) +#define sfTakerPaysCurrency ((10U << 16U) + 1U) +#define sfTakerPaysIssuer ((10U << 16U) + 2U) +#define sfTakerGetsCurrency ((10U << 16U) + 3U) +#define sfTakerGetsIssuer ((10U << 16U) + 4U) +#define sfLedgerHash ((5U << 16U) + 1U) +#define sfParentHash ((5U << 16U) + 2U) +#define sfTransactionHash ((5U << 16U) + 3U) +#define sfAccountHash ((5U << 16U) + 4U) +#define sfPreviousTxnID ((5U << 16U) + 5U) +#define sfLedgerIndex ((5U << 16U) + 6U) +#define sfWalletLocator ((5U << 16U) + 7U) +#define sfRootIndex ((5U << 16U) + 8U) +#define sfAccountTxnID ((5U << 16U) + 9U) +#define sfNFTokenID ((5U << 16U) + 10U) +#define sfEmitParentTxnID ((5U << 16U) + 11U) +#define sfEmitNonce ((5U << 16U) + 12U) +#define sfEmitHookHash ((5U << 16U) + 13U) +#define sfBookDirectory ((5U << 16U) + 16U) +#define sfInvoiceID ((5U << 16U) + 17U) +#define sfNickname ((5U << 16U) + 18U) +#define sfAmendment ((5U << 16U) + 19U) +#define sfHookOn ((5U << 16U) + 20U) +#define sfDigest ((5U << 16U) + 21U) +#define sfChannel ((5U << 16U) + 22U) +#define sfConsensusHash ((5U << 16U) + 23U) +#define sfCheckID ((5U << 16U) + 24U) +#define sfValidatedHash ((5U << 16U) + 25U) +#define sfPreviousPageMin ((5U << 16U) + 26U) +#define sfNextPageMin ((5U << 16U) + 27U) +#define sfNFTokenBuyOffer ((5U << 16U) + 28U) +#define sfNFTokenSellOffer ((5U << 16U) + 29U) +#define sfHookStateKey ((5U << 16U) + 30U) +#define sfHookHash ((5U << 16U) + 31U) +#define sfHookNamespace ((5U << 16U) + 32U) +#define sfHookSetTxnID ((5U << 16U) + 33U) +#define sfOfferID ((5U << 16U) + 34U) +#define sfEscrowID ((5U << 16U) + 35U) +#define sfURITokenID ((5U << 16U) + 36U) +#define sfAmount ((6U << 16U) + 1U) +#define sfBalance ((6U << 16U) + 2U) +#define sfLimitAmount ((6U << 16U) + 3U) +#define sfTakerPays ((6U << 16U) + 4U) +#define sfTakerGets ((6U << 16U) + 5U) +#define sfLowLimit ((6U << 16U) + 6U) +#define sfHighLimit ((6U << 16U) + 7U) +#define sfFee ((6U << 16U) + 8U) +#define sfSendMax ((6U << 16U) + 9U) +#define sfDeliverMin ((6U << 16U) + 10U) +#define sfMinimumOffer ((6U << 16U) + 16U) +#define sfRippleEscrow ((6U << 16U) + 17U) +#define sfDeliveredAmount ((6U << 16U) + 18U) +#define sfNFTokenBrokerFee ((6U << 16U) + 19U) +#define sfHookCallbackFee ((6U << 16U) + 20U) +#define sfLockedBalance ((6U << 16U) + 21U) +#define sfPublicKey ((7U << 16U) + 1U) +#define sfMessageKey ((7U << 16U) + 2U) +#define sfSigningPubKey ((7U << 16U) + 3U) +#define sfTxnSignature ((7U << 16U) + 4U) +#define sfURI ((7U << 16U) + 5U) +#define sfSignature ((7U << 16U) + 6U) +#define sfDomain ((7U << 16U) + 7U) +#define sfFundCode ((7U << 16U) + 8U) +#define sfRemoveCode ((7U << 16U) + 9U) +#define sfExpireCode ((7U << 16U) + 10U) +#define sfCreateCode ((7U << 16U) + 11U) +#define sfMemoType ((7U << 16U) + 12U) +#define sfMemoData ((7U << 16U) + 13U) +#define sfMemoFormat ((7U << 16U) + 14U) +#define sfFulfillment ((7U << 16U) + 16U) +#define sfCondition ((7U << 16U) + 17U) +#define sfMasterSignature ((7U << 16U) + 18U) +#define sfUNLModifyValidator ((7U << 16U) + 19U) +#define sfValidatorToDisable ((7U << 16U) + 20U) +#define sfValidatorToReEnable ((7U << 16U) + 21U) +#define sfHookStateData ((7U << 16U) + 22U) +#define sfHookReturnString ((7U << 16U) + 23U) +#define sfHookParameterName ((7U << 16U) + 24U) +#define sfHookParameterValue ((7U << 16U) + 25U) +#define sfBlob ((7U << 16U) + 26U) +#define sfAccount ((8U << 16U) + 1U) +#define sfOwner ((8U << 16U) + 2U) +#define sfDestination ((8U << 16U) + 3U) +#define sfIssuer ((8U << 16U) + 4U) +#define sfAuthorize ((8U << 16U) + 5U) +#define sfUnauthorize ((8U << 16U) + 6U) +#define sfRegularKey ((8U << 16U) + 8U) +#define sfNFTokenMinter ((8U << 16U) + 9U) +#define sfEmitCallback ((8U << 16U) + 10U) +#define sfHookAccount ((8U << 16U) + 16U) +#define sfIndexes ((19U << 16U) + 1U) +#define sfHashes ((19U << 16U) + 2U) +#define sfAmendments ((19U << 16U) + 3U) +#define sfNFTokenOffers ((19U << 16U) + 4U) +#define sfHookNamespaces ((19U << 16U) + 5U) +#define sfPaths ((18U << 16U) + 1U) +#define sfTransactionMetaData ((14U << 16U) + 2U) +#define sfCreatedNode ((14U << 16U) + 3U) +#define sfDeletedNode ((14U << 16U) + 4U) +#define sfModifiedNode ((14U << 16U) + 5U) +#define sfPreviousFields ((14U << 16U) + 6U) +#define sfFinalFields ((14U << 16U) + 7U) +#define sfNewFields ((14U << 16U) + 8U) +#define sfTemplateEntry ((14U << 16U) + 9U) +#define sfMemo ((14U << 16U) + 10U) +#define sfSignerEntry ((14U << 16U) + 11U) +#define sfNFToken ((14U << 16U) + 12U) +#define sfEmitDetails ((14U << 16U) + 13U) +#define sfHook ((14U << 16U) + 14U) +#define sfSigner ((14U << 16U) + 16U) +#define sfMajority ((14U << 16U) + 18U) +#define sfDisabledValidator ((14U << 16U) + 19U) +#define sfEmittedTxn ((14U << 16U) + 20U) +#define sfHookExecution ((14U << 16U) + 21U) +#define sfHookDefinition ((14U << 16U) + 22U) +#define sfHookParameter ((14U << 16U) + 23U) +#define sfHookGrant ((14U << 16U) + 24U) +#define sfSigners ((15U << 16U) + 3U) +#define sfSignerEntries ((15U << 16U) + 4U) +#define sfTemplate ((15U << 16U) + 5U) +#define sfNecessary ((15U << 16U) + 6U) +#define sfSufficient ((15U << 16U) + 7U) +#define sfAffectedNodes ((15U << 16U) + 8U) +#define sfMemos ((15U << 16U) + 9U) +#define sfNFTokens ((15U << 16U) + 10U) +#define sfHooks ((15U << 16U) + 11U) +#define sfMajorities ((15U << 16U) + 16U) +#define sfDisabledValidators ((15U << 16U) + 17U) +#define sfHookExecutions ((15U << 16U) + 18U) +#define sfHookParameters ((15U << 16U) + 19U) +#define sfHookGrants ((15U << 16U) + 20U) +#define sfActiveValidators ((15U << 16U) + 95U) \ No newline at end of file diff --git a/hook/genesis/headers/types.h b/hook/genesis/headers/types.h new file mode 100644 index 000000000..2a3c66f98 --- /dev/null +++ b/hook/genesis/headers/types.h @@ -0,0 +1,239 @@ +#include + +// 8 byte-int = 1 bytes +#define SFL_CLOSERESOLUTION 1 +#define SFL_METHOD 1 +#define SFL_TRANSACTIONRESULT 1 +#define SFL_TICKSIZE 1 +#define SFL_UNLMODIFYDISABLING 1 +#define SFL_HOOKRESULT 1 +// 16 byte-int = 2 bytes +#define SFL_LEDGERENTRYTYPE 2 +#define SFL_TRANSACTIONTYPE 2 +#define SFL_SIGNERWEIGHT 2 +#define SFL_TRANSFERFEE 2 +#define SFL_VERSION 2 +#define SFL_HOOKSTATECHANGECOUNT 2 +#define SFL_HOOKEMITCOUNT 2 +#define SFL_HOOKEXECUTIONINDEX 2 +#define SFL_HOOKAPIVERSION 2 +// 32 byte-int = 4 bytes +#define SFL_NETWORKID 4 +#define SFL_FLAGS 4 +#define SFL_SOURCETAG 4 +#define SFL_SEQUENCE 4 +#define SFL_PREVIOUSTXNLGRSEQ 4 +#define SFL_LEDGERSEQUENCE 4 +#define SFL_CLOSETIME 4 +#define SFL_PARENTCLOSETIME 4 +#define SFL_SIGNINGTIME 4 +#define SFL_EXPIRATION 4 +#define SFL_TRANSFERRATE 4 +#define SFL_WALLETSIZE 4 +#define SFL_OWNERCOUNT 4 +#define SFL_DESTINATIONTAG 4 +#define SFL_HIGHQUALITYIN 4 +#define SFL_HIGHQUALITYOUT 4 +#define SFL_LOWQUALITYIN 4 +#define SFL_LOWQUALITYOUT 4 +#define SFL_QUALITYIN 4 +#define SFL_QUALITYOUT 4 +#define SFL_STAMPESCROW 4 +#define SFL_BONDAMOUNT 4 +#define SFL_LOADFEE 4 +#define SFL_OFFERSEQUENCE 4 +#define SFL_FIRSTLEDGERSEQUENCE 4 +#define SFL_LASTLEDGERSEQUENCE 4 +#define SFL_TRANSACTIONINDEX 4 +#define SFL_OPERATIONLIMIT 4 +#define SFL_REFERENCEFEEUNITS 4 +#define SFL_RESERVEBASE 4 +#define SFL_RESERVEINCREMENT 4 +#define SFL_SETFLAG 4 +#define SFL_CLEARFLAG 4 +#define SFL_SIGNERQUORUM 4 +#define SFL_CANCELAFTER 4 +#define SFL_FINISHAFTER 4 +#define SFL_SIGNERLISTID 4 +#define SFL_SETTLEDELAY 4 +#define SFL_TICKETCOUNT 4 +#define SFL_TICKETSEQUENCE 4 +#define SFL_NFTOKENTAXON 4 +#define SFL_MINTEDNFTOKENS 4 +#define SFL_BURNEDNFTOKENS 4 +#define SFL_HOOKSTATECOUNT 4 +#define SFL_EMITGENERATION 4 +#define SFL_LOCKCOUNT 4 +#define SFL_REWARDTIME 4 +#define SFL_REWARDLGRFIRST 4 +#define SFL_REWARDLGRLAST 4 +#define SFL_FIRSTNFTOKENSEQUENCE 4 +// 64 byte-int = 8 bytes +#define SFL_INDEX_NEXT 8 +#define SFL_INDEX_PREVIOUS 8 +#define SFL_BOOK_NODE 8 +#define SFL_OWNER_NODE 8 +#define SFL_BASE_FEE 8 +#define SFL_EXCHANGE_RATE 8 +#define SFL_LOW_NODE 8 +#define SFL_HIGH_NODE 8 +#define SFL_DESTINATION_NODE 8 +#define SFL_COOKIE 8 +#define SFL_SERVER_VERSION 8 +#define SFL_EMIT_BURDEN 8 +#define SFL_NFTOKEN_OFFER_NODE 8 +#define SFL_HOOK_INSTRUCTION_COUNT 8 +#define SFL_HOOK_RETURN_CODE 8 +#define SFL_REFERENCE_COUNT 8 +#define SFL_REWARD_ACCUMULATOR 8 +// 128 byte-int = 4 bytes +#define SFL_EMAIL_HASH 128 +// 160 byte-int = 4 bytes +#define SFL_TAKER_PAYS_CURRENCY 160 +#define SFL_TAKER_PAYS_ISSUER 160 +#define SFL_TAKER_GETS_CURRENCY 160 +#define SFL_TAKER_GETS_ISSUER 160 +// 256 byte-int = ??? bytes +#define SFL_LEDGER_HASH 256 +#define SFL_PARENT_HASH 256 +#define SFL_TRANSACTION_HASH 256 +#define SFL_ACCOUNT_HASH 256 +#define SFL_HOOK_ON 256 +#define SFL_PREVIOUS_TXN_ID 256 +#define SFL_LEDGER_INDEX 256 +#define SFL_WALLET_LOCATOR 256 +#define SFL_ROOT_INDEX 256 +#define SFL_ACCOUNT_TXN_ID 256 +#define SFL_NFTOKEN_ID 256 +#define SFL_EMIT_PARENT_TXN_ID 256 +#define SFL_EMIT_NONCE 256 +#define SFL_EMIT_HOOK_HASH 256 +// 256 byte-int = ??? bytes +#define SFL_BOOK_DIRECTORY 256 +#define SFL_INVOICE_ID 256 +#define SFL_NICKNAME 256 +#define SFL_AMENDMENT 256 +#define SFL_DIGEST 256 +#define SFL_CHANNEL 256 +#define SFL_CONSENSUS_HASH 256 +#define SFL_CHECK_ID 256 +#define SFL_VALIDATED_HASH 256 +#define SFL_PREVIOUS_PAGE_MIN 256 +#define SFL_NEXT_PAGE_MIN 256 +#define SFL_NFTOKEN_BUY_OFFER 256 +#define SFL_NFTOKEN_SELL_OFFER 256 +#define SFL_HOOK_STATE_KEY 256 +#define SFL_HOOK_HASH 256 +#define SFL_HOOK_NAMESPACE 256 +#define SFL_HOOK_SET_TXN_ID 256 +#define SFL_OFFER_ID 256 +#define SFL_ESCROW_ID 256 +#define SFL_URITOKEN_ID 256 +// 20 bytes +#define SFL_AMOUNT 20 +#define SFL_BALANCE 20 +#define SFL_LIMIT_AMOUNT 20 +#define SFL_TAKER_PAYS 20 +#define SFL_TAKER_GETS 20 +#define SFL_LOW_LIMIT 20 +#define SFL_HIGH_LIMIT 20 +#define SFL_FEE 20 +#define SFL_SEND_MAX 20 +#define SFL_DELIVER_MIN 20 +#define SFL_LOCKED_BALANCE 20 +// Unimplemented +#define SFL_AMOUNT_MINIMUM_OFFER 8 +#define SFL_AMOUNT_RIPPLE_ESCROW 8 +#define SFL_AMOUNT_DELIVERED_AMOUNT 8 +#define SFL_AMOUNT_NFTOKEN_BROKER_FEE 8 +#define SFL_AMOUNT_HOOK_CALLBACK_FEE 8 +#define SFL_AMOUNT_BASE_FEE_DROPS 8 +#define SFL_AMOUNT_RESERVE_BASE_DROPS 8 +#define SFL_AMOUNT_RESERVE_INCREMENT_DROPS 8 +// Unimplemented +#define SFL_VL_PUBLIC_KEY 64 +#define SFL_VL_MESSAGE_KEY 64 +#define SFL_VL_SIGNING_PUB_KEY 64 +// Unimplemented +#define SFL_VL_TXN_SIGNATURE 96 +// Unimplemented +#define SFL_VL_URI 256 +// Unimplemented +#define SFL_VL_SIGNATURE 96 +// Unimplemented +#define SFL_VL_DOMAIN 256 +#define SFL_VL_FUND_CODE 256 +#define SFL_VL_REMOVE_CODE 256 +#define SFL_VL_EXPIRE_CODE 256 +#define SFL_VL_CREATE_CODE 256 +#define SFL_VL_MEMO_TYPE 256 +#define SFL_VL_MEMO_DATA 256 +#define SFL_VL_MEMO_FORMAT 256 +#define SFL_VL_FULFILLMENT 256 +#define SFL_VL_CONDITION 256 +// Unimplemented +#define SFL_VL_MASTER_SIGNATURE 96 +// Unimplemented +#define SFL_VL_UNL_MODIFY_VALIDATOR 256 +#define SFL_VL_VALIDATOR_TO_DISABLE 256 +#define SFL_VL_VALIDATOR_TO_RE_ENABLE 256 +#define SFL_VL_HOOK_STATE_DATA 256 +#define SFL_VL_HOOK_RETURN_STRING 256 +#define SFL_VL_HOOK_PARAMETER_NAME 256 +#define SFL_VL_HOOK_PARAMETER_VALUE 256 +#define SFL_VL_BLOB 256 +// 20 bytes +#define SFL_ACCOUNT 20 +#define SFL_OWNER 20 +#define SFL_DESTINATION 20 +#define SFL_ISSUER 20 +#define SFL_AUTHORIZE 20 +#define SFL_UNAUTHORIZE 20 +#define SFL_REGULAR_KEY 20 +#define SFL_NFTOKEN_MINTER 20 +#define SFL_EMIT_CALLBACK 20 +#define SFL_HOOK_ACCOUNT 20 +#define SFL_NFTOKEN_MINTER 20 +// Unimplemented +#define SFL_PATHS 1 +// Unimplemented +#define SFL_VECTOR256_INDEXES 32 +#define SFL_VECTOR256_HASHES 32 +#define SFL_VECTOR256_AMENDMENTS 32 +#define SFL_VECTOR256_NFTOKEN_OFFERS 32 +#define SFL_VECTOR256_HOOK_NAMESPACES 32 +// Unimplemented +#define SFL_TRANSACTION_META_DATA 1 +#define SFL_CREATED_NODE 1 +#define SFL_DELETED_NODE 1 +#define SFL_MODIFIED_NODE 1 +#define SFL_PREVIOUS_FIELDS 1 +#define SFL_FINAL_FIELDS 1 +#define SFL_NEW_FIELDS 1 +#define SFL_TEMPLATE_ENTRY 1 +#define SFL_MEMO 1 +#define SFL_SIGNER_ENTRY 1 +#define SFL_NFTOKEN 1 +#define SFL_EMIT_DETAILS 1 +#define SFL_HOOK 1 +#define SFL_SIGNER 1 +#define SFL_MAJORITY 1 +#define SFL_DISABLED_VALIDATOR 1 +#define SFL_EMITTED_TXN 1 +#define SFL_HOOK_EXECUTION 1 +#define SFL_HOOK_DEFINITION 1 +#define SFL_HOOK_PARAMETER 1 +#define SFL_HOOK_GRANT 1 +#define SFL_SIGNERS 1 +#define SFL_SIGNER_ENTRIES 1 +#define SFL_TEMPLATE 1 +#define SFL_NECESSARY 1 +#define SFL_SUFFICIENT 1 +#define SFL_AFFECTED_NODES 1 +#define SFL_MEMOS 1 +#define SFL_NFTOKENS 1 +#define SFL_HOOKS 1 +#define SFL_MAJORITIES 1 +#define SFL_DISABLED_VALIDATORS 1 +#define SFL_HOOK_EXECUTIONS 1 +#define SFL_HOOK_EXECUTION 1 diff --git a/hook/genesis/makefile b/hook/genesis/makefile index 953e86441..7df39f21a 100644 --- a/hook/genesis/makefile +++ b/hook/genesis/makefile @@ -1,9 +1,9 @@ all: reward govern mint accept: - wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I../ + wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I./headers hook-cleaner accept.wasm reward: - wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../ + wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I./headers wasm-opt reward.wasm -o reward.wasm \ --shrink-level=100000000 \ --coalesce-locals-learning \ @@ -58,7 +58,7 @@ reward: hook-cleaner reward.wasm guard_checker reward.wasm govern: - wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I../ + wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I./headers wasm-opt govern.wasm -o govern.wasm \ --shrink-level=100000000 \ --coalesce-locals-learning \ @@ -113,7 +113,7 @@ govern: hook-cleaner govern.wasm guard_checker govern.wasm mint: - wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I../ + wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I./headers wasm-opt mint.wasm -o mint.wasm \ --shrink-level=100000000 \ --coalesce-locals-learning \ @@ -142,5 +142,5 @@ mint: hook-cleaner mint.wasm guard_checker mint.wasm nftoken: - wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I../ + wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I./headers hook-cleaner nftoken.wasm diff --git a/include/xrpl/hook/xahau.h b/include/xrpl/hook/xahau.h index 2ebb5baca..0ac449db4 100644 --- a/include/xrpl/hook/xahau.h +++ b/include/xrpl/hook/xahau.h @@ -43,22 +43,22 @@ static const std::vector GovernanceHook = { 0x5FU, 0x73U, 0x75U, 0x62U, 0x61U, 0x72U, 0x72U, 0x61U, 0x79U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x00U, 0x05U, 0x03U, 0x02U, 0x01U, 0x01U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x08U, - 0x01U, 0x7FU, 0x01U, 0x41U, 0xF0U, 0x9AU, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, - 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x16U, 0x0AU, 0xA5U, 0xAFU, 0x00U, - 0x01U, 0xA1U, 0xAFU, 0x00U, 0x03U, 0x09U, 0x7FU, 0x05U, 0x7EU, 0x01U, 0x7CU, + 0x01U, 0x7FU, 0x01U, 0x41U, 0x90U, 0x9BU, 0x04U, 0x0BU, 0x07U, 0x08U, 0x01U, + 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x16U, 0x0AU, 0xB2U, 0xAFU, 0x00U, + 0x01U, 0xAEU, 0xAFU, 0x00U, 0x03U, 0x0AU, 0x7FU, 0x05U, 0x7EU, 0x02U, 0x7CU, 0x23U, 0x00U, 0x41U, 0xB0U, 0x0AU, 0x6BU, 0x22U, 0x00U, 0x24U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x41U, 0x01U, 0x10U, 0x01U, 0x1AU, - 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x02U, 0x41U, 0xD2U, 0x0AU, - 0x41U, 0x01U, 0x10U, 0x02U, 0x42U, 0x02U, 0x51U, 0x04U, 0x40U, 0x41U, 0xC4U, - 0x0AU, 0x41U, 0x06U, 0x20U, 0x00U, 0x31U, 0x00U, 0x81U, 0x01U, 0x20U, 0x00U, + 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x02U, 0x41U, 0x94U, 0x08U, + 0x41U, 0x01U, 0x10U, 0x02U, 0x42U, 0x02U, 0x51U, 0x04U, 0x40U, 0x41U, 0x96U, + 0x08U, 0x41U, 0x06U, 0x20U, 0x00U, 0x31U, 0x00U, 0x81U, 0x01U, 0x20U, 0x00U, 0x31U, 0x00U, 0x80U, 0x01U, 0x42U, 0x08U, 0x86U, 0x84U, 0x10U, 0x03U, 0x1AU, - 0x0BU, 0x10U, 0x04U, 0x42U, 0xE3U, 0x00U, 0x52U, 0x04U, 0x40U, 0x41U, 0xADU, - 0x0DU, 0x41U, 0xCCU, 0x00U, 0x42U, 0x81U, 0x01U, 0x10U, 0x05U, 0x1AU, 0x0BU, - 0x20U, 0x00U, 0x41U, 0x90U, 0x0AU, 0x6AU, 0x41U, 0x0CU, 0x72U, 0x22U, 0x02U, + 0x0BU, 0x10U, 0x04U, 0x42U, 0xE3U, 0x00U, 0x52U, 0x04U, 0x40U, 0x41U, 0x9CU, + 0x08U, 0x41U, 0xCCU, 0x00U, 0x42U, 0x81U, 0x01U, 0x10U, 0x05U, 0x1AU, 0x0BU, + 0x20U, 0x00U, 0x41U, 0x90U, 0x0AU, 0x6AU, 0x41U, 0x0CU, 0x72U, 0x22U, 0x04U, 0x41U, 0x14U, 0x41U, 0x81U, 0x80U, 0x20U, 0x10U, 0x06U, 0x1AU, 0x20U, 0x00U, 0x41U, 0xF0U, 0x09U, 0x6AU, 0x41U, 0x0CU, 0x72U, 0x22U, 0x08U, 0x41U, 0x14U, 0x10U, 0x07U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x00U, 0x29U, 0x03U, 0xFCU, 0x09U, - 0x22U, 0x0BU, 0x20U, 0x00U, 0x29U, 0x03U, 0x9CU, 0x0AU, 0x51U, 0x04U, 0x40U, + 0x22U, 0x0CU, 0x20U, 0x00U, 0x29U, 0x03U, 0x9CU, 0x0AU, 0x51U, 0x04U, 0x40U, 0x20U, 0x00U, 0x28U, 0x02U, 0x8CU, 0x0AU, 0x20U, 0x00U, 0x28U, 0x02U, 0xACU, 0x0AU, 0x47U, 0x20U, 0x00U, 0x29U, 0x03U, 0x84U, 0x0AU, 0x20U, 0x00U, 0x29U, 0x03U, 0xA4U, 0x0AU, 0x52U, 0x72U, 0x0DU, 0x01U, 0x20U, 0x00U, 0x41U, 0x80U, @@ -67,751 +67,757 @@ static const std::vector GovernanceHook = { 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, 0x80U, 0x01U, 0x51U, 0x04U, 0x40U, 0x20U, 0x00U, 0x29U, 0x03U, 0x84U, 0x0AU, 0x20U, 0x00U, 0x29U, 0x03U, 0x88U, 0x01U, 0x51U, 0x04U, 0x40U, 0x20U, 0x00U, 0x28U, 0x02U, 0x8CU, 0x0AU, 0x20U, 0x00U, - 0x28U, 0x02U, 0x90U, 0x01U, 0x46U, 0x0DU, 0x02U, 0x0BU, 0x0BU, 0x41U, 0xA5U, - 0x0FU, 0x41U, 0x21U, 0x42U, 0x8FU, 0x01U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, - 0x20U, 0x00U, 0x29U, 0x03U, 0xFCU, 0x09U, 0x21U, 0x0BU, 0x0BU, 0x0BU, 0x02U, - 0x7FU, 0x41U, 0xB0U, 0x1AU, 0x29U, 0x03U, 0x00U, 0x20U, 0x0BU, 0x51U, 0x04U, - 0x40U, 0x20U, 0x00U, 0x28U, 0x02U, 0x8CU, 0x0AU, 0x41U, 0xC0U, 0x1AU, 0x28U, - 0x02U, 0x00U, 0x47U, 0x20U, 0x00U, 0x29U, 0x03U, 0x84U, 0x0AU, 0x41U, 0xB8U, - 0x1AU, 0x29U, 0x03U, 0x00U, 0x52U, 0x72U, 0x45U, 0x04U, 0x40U, 0x41U, 0x01U, - 0x21U, 0x04U, 0x41U, 0x93U, 0x13U, 0x0CU, 0x02U, 0x0BU, 0x0BU, 0x41U, 0xE0U, - 0x12U, 0x0BU, 0x41U, 0x33U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, - 0x08U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0xD5U, 0x0AU, 0x41U, 0x02U, - 0x10U, 0x09U, 0x22U, 0x0BU, 0x42U, 0x7BU, 0x51U, 0x04U, 0x40U, 0x20U, 0x00U, - 0x41U, 0x90U, 0x09U, 0x6AU, 0x41U, 0x01U, 0x41U, 0xD4U, 0x0AU, 0x41U, 0x03U, - 0x10U, 0x0AU, 0x42U, 0x00U, 0x53U, 0x04U, 0x40U, 0x41U, 0x9EU, 0x18U, 0x41U, - 0x3AU, 0x42U, 0xA3U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x9CU, 0x0AU, + 0x28U, 0x02U, 0x90U, 0x01U, 0x46U, 0x0DU, 0x02U, 0x0BU, 0x0BU, 0x41U, 0xE8U, + 0x08U, 0x41U, 0x21U, 0x42U, 0x8FU, 0x01U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, + 0x20U, 0x00U, 0x29U, 0x03U, 0xFCU, 0x09U, 0x21U, 0x0CU, 0x0BU, 0x0BU, 0x02U, + 0x7FU, 0x41U, 0x80U, 0x08U, 0x29U, 0x03U, 0x00U, 0x20U, 0x0CU, 0x51U, 0x04U, + 0x40U, 0x20U, 0x00U, 0x28U, 0x02U, 0x8CU, 0x0AU, 0x41U, 0x90U, 0x08U, 0x28U, + 0x02U, 0x00U, 0x47U, 0x20U, 0x00U, 0x29U, 0x03U, 0x84U, 0x0AU, 0x41U, 0x88U, + 0x08U, 0x29U, 0x03U, 0x00U, 0x52U, 0x72U, 0x45U, 0x04U, 0x40U, 0x41U, 0x01U, + 0x21U, 0x02U, 0x41U, 0x89U, 0x09U, 0x0CU, 0x02U, 0x0BU, 0x0BU, 0x41U, 0xBCU, + 0x09U, 0x0BU, 0x41U, 0x33U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, + 0x08U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0xEFU, 0x09U, 0x41U, 0x02U, + 0x10U, 0x09U, 0x22U, 0x0CU, 0x42U, 0x7BU, 0x51U, 0x04U, 0x40U, 0x20U, 0x00U, + 0x41U, 0x90U, 0x09U, 0x6AU, 0x41U, 0x01U, 0x41U, 0xF2U, 0x09U, 0x41U, 0x03U, + 0x10U, 0x0AU, 0x42U, 0x7FU, 0x57U, 0x04U, 0x40U, 0x41U, 0xF6U, 0x09U, 0x41U, + 0x3AU, 0x42U, 0xA3U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0xB0U, 0x0AU, 0x41U, 0x03U, 0x20U, 0x00U, 0x31U, 0x00U, 0x90U, 0x09U, 0x10U, 0x03U, 0x1AU, - 0x20U, 0x00U, 0x41U, 0x90U, 0x09U, 0x6AU, 0x41U, 0x01U, 0x41U, 0xD5U, 0x0AU, - 0x41U, 0x02U, 0x10U, 0x0CU, 0x42U, 0x00U, 0x57U, 0x04U, 0x40U, 0x41U, 0x83U, - 0x14U, 0x41U, 0x1AU, 0x42U, 0xA7U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, - 0x80U, 0x08U, 0x41U, 0x0CU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x90U, 0x09U, 0x22U, - 0x05U, 0xADU, 0x42U, 0xFFU, 0x01U, 0x83U, 0x22U, 0x0BU, 0x10U, 0x03U, 0x1AU, + 0x20U, 0x00U, 0x41U, 0x90U, 0x09U, 0x6AU, 0x41U, 0x01U, 0x41U, 0xEFU, 0x09U, + 0x41U, 0x02U, 0x10U, 0x0CU, 0x42U, 0x00U, 0x57U, 0x04U, 0x40U, 0x41U, 0xB4U, + 0x0AU, 0x41U, 0x1AU, 0x42U, 0xA7U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, + 0xCEU, 0x0AU, 0x41U, 0x0CU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x90U, 0x09U, 0x22U, + 0x03U, 0xADU, 0x42U, 0xFFU, 0x01U, 0x83U, 0x22U, 0x0CU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x90U, 0x09U, 0x22U, 0x01U, 0x45U, 0x04U, 0x40U, - 0x41U, 0x8EU, 0x16U, 0x41U, 0x2EU, 0x42U, 0xAEU, 0x01U, 0x10U, 0x0BU, 0x1AU, + 0x41U, 0xDBU, 0x0AU, 0x41U, 0x2EU, 0x42U, 0xAEU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x90U, 0x09U, 0x21U, 0x01U, 0x0BU, 0x20U, 0x01U, - 0x41U, 0xFFU, 0x01U, 0x71U, 0x41U, 0x15U, 0x4FU, 0x04U, 0x40U, 0x41U, 0xD8U, - 0x18U, 0x41U, 0x3DU, 0x42U, 0xB1U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, - 0x04U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, - 0x41U, 0x08U, 0x41U, 0xC0U, 0x0AU, 0x41U, 0x03U, 0x10U, 0x0AU, 0x42U, 0x00U, - 0x53U, 0x04U, 0x40U, 0x41U, 0xAEU, 0x17U, 0x41U, 0x39U, 0x42U, 0xB6U, 0x01U, + 0x41U, 0xFFU, 0x01U, 0x71U, 0x41U, 0x15U, 0x4FU, 0x04U, 0x40U, 0x41U, 0x89U, + 0x0BU, 0x41U, 0x3DU, 0x42U, 0xB1U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, + 0x02U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, + 0x41U, 0x08U, 0x41U, 0xC6U, 0x0BU, 0x41U, 0x03U, 0x10U, 0x0AU, 0x42U, 0x7FU, + 0x57U, 0x04U, 0x40U, 0x41U, 0xCAU, 0x0BU, 0x41U, 0x39U, 0x42U, 0xB6U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x08U, - 0x41U, 0xD0U, 0x0AU, 0x41U, 0x03U, 0x10U, 0x0AU, 0x42U, 0x00U, 0x53U, 0x04U, - 0x40U, 0x41U, 0xE7U, 0x17U, 0x41U, 0x37U, 0x42U, 0xB9U, 0x01U, 0x10U, 0x0BU, + 0x41U, 0x83U, 0x0CU, 0x41U, 0x03U, 0x10U, 0x0AU, 0x42U, 0x7FU, 0x57U, 0x04U, + 0x40U, 0x41U, 0x87U, 0x0CU, 0x41U, 0x37U, 0x42U, 0xB9U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x29U, 0x03U, 0x20U, 0x50U, 0x04U, 0x40U, 0x41U, - 0xE0U, 0x15U, 0x41U, 0x2EU, 0x42U, 0xBCU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, - 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x41U, 0x08U, 0x41U, 0xC1U, 0x0AU, - 0x41U, 0x02U, 0x10U, 0x0CU, 0x42U, 0x00U, 0x57U, 0x04U, 0x40U, 0x41U, 0x83U, - 0x14U, 0x41U, 0x1AU, 0x42U, 0xBFU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, - 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x08U, 0x41U, 0xD1U, 0x0AU, 0x41U, 0x02U, - 0x10U, 0x0CU, 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U, 0x83U, 0x14U, 0x41U, - 0x1AU, 0x42U, 0xC2U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x03U, 0x40U, - 0x41U, 0xC5U, 0x81U, 0x80U, 0x80U, 0x78U, 0x41U, 0x15U, 0x10U, 0x00U, 0x1AU, - 0x02U, 0x40U, 0x20U, 0x00U, 0x20U, 0x03U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, - 0x2DU, 0x00U, 0x00U, 0x22U, 0x01U, 0x20U, 0x05U, 0x4FU, 0x0DU, 0x00U, 0x20U, - 0x00U, 0x20U, 0x01U, 0x3AU, 0x00U, 0x52U, 0x20U, 0x00U, 0x41U, 0xC9U, 0xA6U, - 0x01U, 0x3BU, 0x00U, 0x50U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, - 0x14U, 0x20U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x41U, 0x03U, 0x10U, 0x0AU, - 0x42U, 0x14U, 0x52U, 0x04U, 0x40U, 0x41U, 0x99U, 0x09U, 0x41U, 0x3FU, 0x42U, - 0xCAU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0xEEU, 0x0BU, 0x41U, 0x08U, - 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x14U, 0x41U, - 0x01U, 0x10U, 0x08U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x14U, 0x20U, 0x00U, 0x41U, - 0x01U, 0x10U, 0x0CU, 0x42U, 0x14U, 0x52U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, - 0x41U, 0x1AU, 0x42U, 0xD0U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, - 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x14U, 0x10U, - 0x0CU, 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, - 0x42U, 0xD4U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x2DU, 0x00U, - 0x00U, 0x41U, 0x01U, 0x6AU, 0x21U, 0x03U, 0x0CU, 0x01U, 0x0BU, 0x0BU, 0x41U, - 0xD3U, 0x0CU, 0x41U, 0x2AU, 0x42U, 0xD7U, 0x01U, 0x10U, 0x05U, 0x1AU, 0x0BU, - 0x41U, 0x80U, 0x08U, 0x41U, 0x0CU, 0x20U, 0x0BU, 0x10U, 0x03U, 0x1AU, 0x41U, - 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x41U, 0x14U, 0x10U, 0x09U, 0x42U, 0x00U, - 0x53U, 0x04U, 0x40U, 0x41U, 0x9BU, 0x12U, 0x41U, 0xC5U, 0x00U, 0x42U, 0xE3U, - 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0xEEU, 0x09U, 0x6AU, - 0x41U, 0x02U, 0x41U, 0xBEU, 0x0AU, 0x41U, 0x01U, 0x10U, 0x02U, 0x21U, 0x0AU, - 0x20U, 0x00U, 0x2DU, 0x00U, 0xEEU, 0x09U, 0x21U, 0x02U, 0x20U, 0x00U, 0x20U, - 0x00U, 0x2DU, 0x00U, 0xEFU, 0x09U, 0x22U, 0x01U, 0x3AU, 0x00U, 0xEDU, 0x09U, - 0x02U, 0x40U, 0x20U, 0x0AU, 0x42U, 0x02U, 0x51U, 0x04U, 0x40U, 0x02U, 0x40U, - 0x20U, 0x02U, 0x41U, 0xC8U, 0x00U, 0x6BU, 0x22U, 0x03U, 0x41U, 0x0BU, 0x4BU, - 0x0DU, 0x00U, 0x41U, 0x01U, 0x20U, 0x03U, 0x74U, 0x41U, 0x81U, 0x18U, 0x71U, - 0x0DU, 0x02U, 0x0BU, 0x0BU, 0x41U, 0x9BU, 0x0EU, 0x41U, 0x3DU, 0x42U, 0xF2U, - 0x01U, 0x10U, 0x0BU, 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x21U, - 0x01U, 0x0BU, 0x20U, 0x02U, 0x41U, 0xD3U, 0x00U, 0x47U, 0x20U, 0x01U, 0x41U, - 0xFFU, 0x01U, 0x71U, 0x41U, 0x14U, 0x49U, 0x72U, 0x04U, 0x40U, 0x02U, 0x40U, - 0x20U, 0x02U, 0x41U, 0xC8U, 0x00U, 0x46U, 0x21U, 0x03U, 0x20U, 0x02U, 0x41U, - 0xC8U, 0x00U, 0x47U, 0x20U, 0x01U, 0x41U, 0xFFU, 0x01U, 0x71U, 0x41U, 0x0BU, - 0x49U, 0x72U, 0x45U, 0x04U, 0x40U, 0x41U, 0xB1U, 0x15U, 0x41U, 0x2FU, 0x42U, - 0xF9U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0CU, 0x01U, 0x0BU, 0x20U, 0x02U, 0x41U, - 0xD2U, 0x00U, 0x47U, 0x0DU, 0x00U, 0x41U, 0x01U, 0x21U, 0x06U, 0x20U, 0x01U, - 0x41U, 0xFFU, 0x01U, 0x71U, 0x22U, 0x01U, 0x41U, 0xC4U, 0x00U, 0x46U, 0x20U, - 0x01U, 0x41U, 0xD2U, 0x00U, 0x46U, 0x72U, 0x0DU, 0x00U, 0x41U, 0xF2U, 0x16U, - 0x41U, 0x3CU, 0x42U, 0xFCU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x05U, 0x41U, - 0x81U, 0x15U, 0x41U, 0x30U, 0x42U, 0xF6U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x41U, - 0x00U, 0x21U, 0x03U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x01U, 0x3AU, 0x00U, 0xECU, - 0x09U, 0x20U, 0x04U, 0x45U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, - 0xECU, 0x09U, 0x6AU, 0x41U, 0x01U, 0x41U, 0xCCU, 0x0AU, 0x41U, 0x01U, 0x10U, - 0x02U, 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, 0xD8U, 0x0AU, 0x41U, 0xC1U, - 0x00U, 0x42U, 0x84U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x88U, 0x09U, - 0x41U, 0x01U, 0x20U, 0x00U, 0x31U, 0x00U, 0xECU, 0x09U, 0x10U, 0x03U, 0x1AU, - 0x20U, 0x06U, 0x45U, 0x21U, 0x06U, 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, - 0x22U, 0x01U, 0x41U, 0x03U, 0x6BU, 0x41U, 0xFFU, 0x01U, 0x71U, 0x41U, 0xFDU, - 0x01U, 0x4DU, 0x04U, 0x40U, 0x41U, 0x95U, 0x19U, 0x41U, 0x30U, 0x42U, 0x89U, - 0x02U, 0x10U, 0x0BU, 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x21U, - 0x01U, 0x0BU, 0x20U, 0x01U, 0x41U, 0xFFU, 0x01U, 0x71U, 0x41U, 0x02U, 0x47U, - 0x20U, 0x06U, 0x72U, 0x0DU, 0x00U, 0x41U, 0x99U, 0x0BU, 0x41U, 0xC5U, 0x00U, + 0xBEU, 0x0CU, 0x41U, 0x2EU, 0x42U, 0xBCU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, + 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x41U, 0x08U, 0x41U, 0xECU, 0x0CU, + 0x41U, 0x02U, 0x10U, 0x0CU, 0x42U, 0x00U, 0x57U, 0x04U, 0x40U, 0x41U, 0xB4U, + 0x0AU, 0x41U, 0x1AU, 0x42U, 0xBFU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, + 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x08U, 0x41U, 0xEFU, 0x0CU, 0x41U, 0x02U, + 0x10U, 0x0CU, 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U, 0xB4U, 0x0AU, 0x41U, + 0x1AU, 0x42U, 0xC2U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0x00U, + 0x21U, 0x01U, 0x03U, 0x40U, 0x41U, 0xC5U, 0x81U, 0x80U, 0x80U, 0x78U, 0x41U, + 0x15U, 0x10U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x00U, 0x20U, 0x01U, 0x3AU, + 0x00U, 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0x00U, 0x22U, 0x01U, 0x20U, 0x03U, + 0x4FU, 0x0DU, 0x00U, 0x20U, 0x00U, 0x20U, 0x01U, 0x3AU, 0x00U, 0x52U, 0x20U, + 0x00U, 0x41U, 0xC9U, 0xA6U, 0x01U, 0x3BU, 0x00U, 0x50U, 0x20U, 0x00U, 0x41U, + 0x80U, 0x01U, 0x6AU, 0x41U, 0x14U, 0x20U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, + 0x41U, 0x03U, 0x10U, 0x0AU, 0x42U, 0x14U, 0x52U, 0x04U, 0x40U, 0x41U, 0xF2U, + 0x0CU, 0x41U, 0x3FU, 0x42U, 0xCAU, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, + 0xB1U, 0x0DU, 0x41U, 0x08U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x22U, + 0x01U, 0x41U, 0x14U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x20U, 0x01U, 0x41U, + 0x14U, 0x20U, 0x00U, 0x41U, 0x01U, 0x10U, 0x0CU, 0x42U, 0x14U, 0x52U, 0x04U, + 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xD0U, 0x01U, 0x10U, 0x0BU, + 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, + 0x6AU, 0x41U, 0x14U, 0x10U, 0x0CU, 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, + 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xD4U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, + 0x20U, 0x00U, 0x2DU, 0x00U, 0x00U, 0x41U, 0x01U, 0x6AU, 0x21U, 0x01U, 0x0CU, + 0x01U, 0x0BU, 0x0BU, 0x41U, 0xB9U, 0x0DU, 0x41U, 0x2AU, 0x42U, 0xD7U, 0x01U, + 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xCEU, 0x0AU, 0x41U, 0x0CU, 0x20U, 0x0CU, + 0x10U, 0x03U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x41U, 0x14U, + 0x10U, 0x09U, 0x42U, 0x7FU, 0x57U, 0x04U, 0x40U, 0x41U, 0xE3U, 0x0DU, 0x41U, + 0xC5U, 0x00U, 0x42U, 0xE3U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, + 0x41U, 0xEEU, 0x09U, 0x6AU, 0x41U, 0x02U, 0x41U, 0xA8U, 0x0EU, 0x41U, 0x01U, + 0x10U, 0x02U, 0x21U, 0x0BU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEEU, 0x09U, 0x21U, + 0x04U, 0x20U, 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEFU, 0x09U, 0x22U, 0x01U, + 0x3AU, 0x00U, 0xEDU, 0x09U, 0x02U, 0x40U, 0x20U, 0x0BU, 0x42U, 0x02U, 0x51U, + 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x04U, 0x41U, 0xC8U, 0x00U, 0x6BU, 0x22U, + 0x03U, 0x41U, 0x0BU, 0x4BU, 0x0DU, 0x00U, 0x41U, 0x01U, 0x20U, 0x03U, 0x74U, + 0x41U, 0x81U, 0x18U, 0x71U, 0x0DU, 0x02U, 0x0BU, 0x0BU, 0x41U, 0xAAU, 0x0EU, + 0x41U, 0x3DU, 0x42U, 0xF2U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x20U, 0x00U, 0x2DU, + 0x00U, 0xEDU, 0x09U, 0x21U, 0x01U, 0x0BU, 0x02U, 0x40U, 0x20U, 0x04U, 0x41U, + 0xD3U, 0x00U, 0x46U, 0x04U, 0x40U, 0x20U, 0x01U, 0x41U, 0x14U, 0x4FU, 0x04U, + 0x40U, 0x41U, 0xE7U, 0x0EU, 0x41U, 0x30U, 0x42U, 0xF6U, 0x01U, 0x10U, 0x0BU, + 0x1AU, 0x41U, 0x00U, 0x21U, 0x03U, 0x41U, 0x01U, 0x21U, 0x05U, 0x0CU, 0x02U, + 0x0BU, 0x0BU, 0x20U, 0x04U, 0x41U, 0xC8U, 0x00U, 0x46U, 0x04U, 0x40U, 0x20U, + 0x01U, 0x41U, 0x0BU, 0x4FU, 0x04U, 0x40U, 0x41U, 0x97U, 0x0FU, 0x41U, 0x2FU, + 0x42U, 0xF9U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x41U, 0x01U, 0x21U, 0x05U, 0x41U, + 0x01U, 0x21U, 0x03U, 0x0CU, 0x02U, 0x0BU, 0x0BU, 0x20U, 0x04U, 0x41U, 0xC8U, + 0x00U, 0x46U, 0x21U, 0x03U, 0x41U, 0x01U, 0x21U, 0x05U, 0x20U, 0x04U, 0x41U, + 0xD2U, 0x00U, 0x47U, 0x0DU, 0x00U, 0x41U, 0x00U, 0x21U, 0x05U, 0x20U, 0x01U, + 0x41U, 0xC4U, 0x00U, 0x46U, 0x20U, 0x01U, 0x41U, 0xD2U, 0x00U, 0x46U, 0x72U, + 0x0DU, 0x00U, 0x41U, 0xC6U, 0x0FU, 0x41U, 0x3CU, 0x42U, 0xFCU, 0x01U, 0x10U, + 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x01U, 0x3AU, 0x00U, 0xECU, 0x09U, + 0x20U, 0x02U, 0x45U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0xECU, + 0x09U, 0x6AU, 0x41U, 0x01U, 0x41U, 0x82U, 0x10U, 0x41U, 0x01U, 0x10U, 0x02U, + 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, 0x84U, 0x10U, 0x41U, 0xC1U, 0x00U, + 0x42U, 0x84U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0xC5U, 0x10U, 0x41U, + 0x01U, 0x20U, 0x00U, 0x31U, 0x00U, 0xECU, 0x09U, 0x10U, 0x03U, 0x1AU, 0x20U, + 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x22U, 0x01U, 0x41U, 0x01U, 0x6BU, 0x41U, + 0xFFU, 0x01U, 0x71U, 0x41U, 0x02U, 0x4FU, 0x04U, 0x40U, 0x41U, 0xC7U, 0x10U, + 0x41U, 0x30U, 0x42U, 0x89U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x20U, 0x00U, 0x2DU, + 0x00U, 0xECU, 0x09U, 0x21U, 0x01U, 0x0BU, 0x20U, 0x01U, 0x41U, 0x02U, 0x47U, + 0x20U, 0x05U, 0x72U, 0x0DU, 0x00U, 0x41U, 0xF7U, 0x10U, 0x41U, 0xC5U, 0x00U, 0x42U, 0x8DU, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0x20U, 0x41U, - 0x20U, 0x41U, 0x14U, 0x41U, 0x08U, 0x20U, 0x02U, 0x41U, 0xD3U, 0x00U, 0x46U, + 0x20U, 0x41U, 0x14U, 0x41U, 0x08U, 0x20U, 0x04U, 0x41U, 0xD3U, 0x00U, 0x46U, 0x1BU, 0x20U, 0x03U, 0x1BU, 0x22U, 0x03U, 0x6BU, 0x22U, 0x01U, 0x41U, 0xFCU, - 0x01U, 0x71U, 0x22U, 0x07U, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x6AU, - 0x22U, 0x05U, 0x20U, 0x03U, 0x41U, 0xBCU, 0x0AU, 0x41U, 0x01U, 0x10U, 0x02U, - 0x20U, 0x03U, 0xADU, 0x22U, 0x0AU, 0x52U, 0x04U, 0x40U, 0x41U, 0xD8U, 0x11U, - 0x41U, 0xC3U, 0x00U, 0x42U, 0x9BU, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, - 0x00U, 0x21U, 0x06U, 0x02U, 0x40U, 0x20U, 0x00U, 0x29U, 0x03U, 0xB0U, 0x09U, - 0x50U, 0x04U, 0x40U, 0x20U, 0x00U, 0x29U, 0x03U, 0xB8U, 0x09U, 0x20U, 0x00U, - 0x29U, 0x03U, 0xC0U, 0x09U, 0x84U, 0x42U, 0x00U, 0x52U, 0x0DU, 0x01U, 0x20U, - 0x00U, 0x29U, 0x03U, 0xC8U, 0x09U, 0x50U, 0x21U, 0x06U, 0x0BU, 0x0BU, 0x41U, - 0xDEU, 0x0BU, 0x41U, 0x10U, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x22U, - 0x09U, 0x41U, 0x38U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xF6U, 0x0BU, - 0x41U, 0x0FU, 0x20U, 0x01U, 0xADU, 0x42U, 0xFFU, 0x01U, 0x83U, 0x10U, 0x03U, - 0x1AU, 0x41U, 0x85U, 0x0CU, 0x41U, 0x0CU, 0x20U, 0x0AU, 0x10U, 0x03U, 0x1AU, - 0x41U, 0x91U, 0x0CU, 0x41U, 0x0CU, 0x20U, 0x05U, 0x20U, 0x03U, 0x41U, 0x01U, - 0x10U, 0x08U, 0x1AU, 0x20U, 0x00U, 0x20U, 0x02U, 0x3AU, 0x00U, 0x91U, 0x0AU, - 0x20U, 0x00U, 0x41U, 0xD6U, 0x00U, 0x3AU, 0x00U, 0x90U, 0x0AU, 0x20U, 0x00U, - 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x3AU, 0x00U, 0x92U, 0x0AU, 0x20U, - 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x3AU, 0x00U, 0x93U, 0x0AU, - 0x20U, 0x00U, 0x41U, 0x90U, 0x09U, 0x6AU, 0x22U, 0x01U, 0x20U, 0x07U, 0x6AU, - 0x20U, 0x03U, 0x20U, 0x00U, 0x41U, 0x90U, 0x0AU, 0x6AU, 0x41U, 0x20U, 0x10U, - 0x09U, 0x21U, 0x0CU, 0x41U, 0xA6U, 0x0AU, 0x41U, 0x14U, 0x20U, 0x01U, 0x41U, - 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xAFU, 0x0AU, 0x41U, 0x0BU, - 0x20U, 0x09U, 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xD8U, - 0x09U, 0x41U, 0x14U, 0x20U, 0x0CU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xE1U, 0x09U, - 0x41U, 0x0BU, 0x20U, 0x0AU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x0AU, 0x20U, 0x0CU, - 0x51U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x29U, 0x03U, 0x90U, 0x09U, - 0x20U, 0x00U, 0x29U, 0x03U, 0xB0U, 0x09U, 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, - 0x98U, 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, 0xB8U, 0x09U, 0x52U, 0x72U, 0x20U, - 0x00U, 0x29U, 0x03U, 0xA0U, 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC0U, 0x09U, - 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, 0xA8U, 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, - 0xC8U, 0x09U, 0x52U, 0x72U, 0x72U, 0x0DU, 0x00U, 0x41U, 0xC2U, 0x14U, 0x41U, - 0x3FU, 0x42U, 0xBBU, 0x02U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x05U, - 0x20U, 0x03U, 0x20U, 0x00U, 0x41U, 0x90U, 0x0AU, 0x6AU, 0x41U, 0x20U, 0x10U, - 0x0CU, 0x20U, 0x0AU, 0x52U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, - 0x42U, 0xC2U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x0CU, 0x42U, 0x00U, - 0x55U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x20U, 0x02U, 0x3AU, 0x00U, - 0x91U, 0x09U, 0x20U, 0x00U, 0x41U, 0xC3U, 0x00U, 0x3AU, 0x00U, 0x90U, 0x09U, - 0x20U, 0x00U, 0x41U, 0x00U, 0x3AU, 0x00U, 0x80U, 0x01U, 0x20U, 0x00U, 0x20U, - 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x3AU, 0x00U, 0x92U, 0x09U, 0x20U, 0x00U, - 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x3AU, 0x00U, 0x93U, 0x09U, 0x20U, - 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, 0x90U, - 0x09U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x09U, 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, - 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xCFU, 0x02U, 0x10U, 0x0BU, 0x1AU, - 0x0BU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x80U, 0x01U, 0x22U, 0x01U, 0x45U, 0x04U, - 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xD0U, 0x02U, 0x10U, 0x0BU, - 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0x80U, 0x01U, 0x21U, 0x01U, 0x0BU, 0x20U, - 0x00U, 0x20U, 0x01U, 0x41U, 0x01U, 0x6BU, 0x22U, 0x01U, 0x3AU, 0x00U, 0x80U, - 0x01U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x00U, 0x20U, 0x01U, - 0x41U, 0xFFU, 0x01U, 0x71U, 0x22U, 0x01U, 0x1BU, 0x20U, 0x01U, 0x41U, 0x00U, - 0x47U, 0x20U, 0x00U, 0x41U, 0x90U, 0x09U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, - 0x42U, 0x00U, 0x59U, 0x0DU, 0x00U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, - 0xD4U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x00U, - 0x3AU, 0x00U, 0x8FU, 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, 0xB0U, 0x09U, 0x21U, - 0x0AU, 0x20U, 0x00U, 0x41U, 0xC3U, 0x00U, 0x3AU, 0x00U, 0xB0U, 0x09U, 0x20U, - 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x3AU, 0x00U, 0xB3U, 0x09U, - 0x20U, 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x3AU, 0x00U, 0xB2U, - 0x09U, 0x20U, 0x00U, 0x20U, 0x02U, 0x3AU, 0x00U, 0xB1U, 0x09U, 0x20U, 0x00U, - 0x41U, 0x8FU, 0x09U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, - 0xB0U, 0x09U, 0x6AU, 0x22U, 0x07U, 0x41U, 0x20U, 0x10U, 0x09U, 0x1AU, 0x20U, - 0x00U, 0x20U, 0x00U, 0x2DU, 0x00U, 0x8FU, 0x09U, 0x41U, 0x01U, 0x6AU, 0x3AU, - 0x00U, 0x8FU, 0x09U, 0x20U, 0x01U, 0x41U, 0x01U, 0x20U, 0x07U, 0x41U, 0x20U, - 0x10U, 0x0CU, 0x42U, 0x00U, 0x57U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, - 0x1AU, 0x42U, 0xE4U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x37U, 0x03U, 0xB0U, 0x09U, 0x41U, 0xEDU, 0x08U, 0x41U, 0x0FU, 0x20U, - 0x06U, 0xADU, 0x22U, 0x0DU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xCCU, 0x08U, 0x41U, - 0x05U, 0x20U, 0x00U, 0x31U, 0x00U, 0x8FU, 0x09U, 0x10U, 0x03U, 0x1AU, 0x41U, - 0x80U, 0x08U, 0x41U, 0x0CU, 0x20U, 0x0BU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xA0U, - 0x0AU, 0x41U, 0x06U, 0x20U, 0x00U, 0x41U, 0xEEU, 0x09U, 0x6AU, 0x41U, 0x02U, - 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x20U, 0x0BU, 0xB9U, 0x21U, 0x0FU, 0x02U, - 0x40U, 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x41U, 0x02U, 0x46U, 0x20U, - 0x04U, 0x72U, 0x41U, 0x01U, 0x46U, 0x04U, 0x40U, 0x20U, 0x00U, 0x31U, 0x00U, - 0x8FU, 0x09U, 0x20U, 0x0FU, 0x44U, 0x9AU, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, - 0xE9U, 0x3FU, 0xA2U, 0x22U, 0x0FU, 0x99U, 0x44U, 0x00U, 0x00U, 0x00U, 0x00U, - 0x00U, 0x00U, 0xE0U, 0x43U, 0x63U, 0x04U, 0x7EU, 0x20U, 0x0FU, 0xB0U, 0x05U, + 0x01U, 0x71U, 0x22U, 0x06U, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x6AU, + 0x22U, 0x05U, 0x20U, 0x03U, 0x41U, 0xBCU, 0x11U, 0x41U, 0x01U, 0x10U, 0x02U, + 0x20U, 0x03U, 0xADU, 0x22U, 0x0BU, 0x52U, 0x04U, 0x40U, 0x41U, 0xBEU, 0x11U, + 0x41U, 0xC3U, 0x00U, 0x42U, 0x9BU, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, + 0x00U, 0x29U, 0x03U, 0xB0U, 0x09U, 0x50U, 0x04U, 0x40U, 0x20U, 0x00U, 0x29U, + 0x03U, 0xB8U, 0x09U, 0x42U, 0x00U, 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC0U, + 0x09U, 0x42U, 0x00U, 0x52U, 0x72U, 0x45U, 0x04U, 0x40U, 0x20U, 0x00U, 0x29U, + 0x03U, 0xC8U, 0x09U, 0x50U, 0x21U, 0x07U, 0x0BU, 0x0BU, 0x41U, 0x81U, 0x12U, + 0x41U, 0x10U, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x22U, 0x09U, 0x41U, + 0x38U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0x91U, 0x12U, 0x41U, 0x0FU, + 0x20U, 0x01U, 0xADU, 0x42U, 0xFFU, 0x01U, 0x83U, 0x10U, 0x03U, 0x1AU, 0x41U, + 0xA0U, 0x12U, 0x41U, 0x0CU, 0x20U, 0x0BU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xACU, + 0x12U, 0x41U, 0x0CU, 0x20U, 0x05U, 0x20U, 0x03U, 0x41U, 0x01U, 0x10U, 0x08U, + 0x1AU, 0x20U, 0x00U, 0x20U, 0x04U, 0x3AU, 0x00U, 0x91U, 0x0AU, 0x20U, 0x00U, + 0x41U, 0xD6U, 0x00U, 0x3AU, 0x00U, 0x90U, 0x0AU, 0x20U, 0x00U, 0x20U, 0x00U, + 0x2DU, 0x00U, 0xEDU, 0x09U, 0x3AU, 0x00U, 0x92U, 0x0AU, 0x20U, 0x00U, 0x20U, + 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x3AU, 0x00U, 0x93U, 0x0AU, 0x20U, 0x00U, + 0x41U, 0x90U, 0x09U, 0x6AU, 0x22U, 0x01U, 0x20U, 0x06U, 0x6AU, 0x20U, 0x03U, + 0x20U, 0x00U, 0x41U, 0x90U, 0x0AU, 0x6AU, 0x41U, 0x20U, 0x10U, 0x09U, 0x21U, + 0x0DU, 0x41U, 0xB8U, 0x12U, 0x41U, 0x14U, 0x20U, 0x01U, 0x41U, 0x20U, 0x41U, + 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xCCU, 0x12U, 0x41U, 0x0BU, 0x20U, 0x09U, + 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xD7U, 0x12U, 0x41U, + 0x14U, 0x20U, 0x0DU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xEBU, 0x12U, 0x41U, 0x0BU, + 0x20U, 0x0BU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x0BU, 0x20U, 0x0DU, 0x51U, 0x04U, + 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x29U, 0x03U, 0x90U, 0x09U, 0x20U, 0x00U, + 0x29U, 0x03U, 0xB0U, 0x09U, 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, 0x98U, 0x09U, + 0x20U, 0x00U, 0x29U, 0x03U, 0xB8U, 0x09U, 0x52U, 0x72U, 0x20U, 0x00U, 0x29U, + 0x03U, 0xA0U, 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC0U, 0x09U, 0x52U, 0x20U, + 0x00U, 0x29U, 0x03U, 0xA8U, 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC8U, 0x09U, + 0x52U, 0x72U, 0x72U, 0x0DU, 0x00U, 0x41U, 0xF6U, 0x12U, 0x41U, 0x3FU, 0x42U, + 0xBBU, 0x02U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x05U, 0x20U, 0x03U, + 0x20U, 0x00U, 0x41U, 0x90U, 0x0AU, 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, 0x20U, + 0x0BU, 0x52U, 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xC2U, + 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x0DU, 0x42U, 0x01U, 0x59U, 0x04U, + 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x20U, 0x04U, 0x3AU, 0x00U, 0x91U, 0x09U, + 0x20U, 0x00U, 0x41U, 0xC3U, 0x00U, 0x3AU, 0x00U, 0x90U, 0x09U, 0x20U, 0x00U, + 0x41U, 0x00U, 0x3AU, 0x00U, 0x80U, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x2DU, + 0x00U, 0xEDU, 0x09U, 0x3AU, 0x00U, 0x92U, 0x09U, 0x20U, 0x00U, 0x20U, 0x00U, + 0x2DU, 0x00U, 0xECU, 0x09U, 0x3AU, 0x00U, 0x93U, 0x09U, 0x20U, 0x00U, 0x41U, + 0x80U, 0x01U, 0x6AU, 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, 0x90U, 0x09U, 0x6AU, + 0x41U, 0x20U, 0x10U, 0x09U, 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, 0xB4U, + 0x0AU, 0x41U, 0x1AU, 0x42U, 0xCFU, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, + 0x00U, 0x2DU, 0x00U, 0x80U, 0x01U, 0x22U, 0x01U, 0x45U, 0x04U, 0x40U, 0x41U, + 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xD0U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x20U, + 0x00U, 0x2DU, 0x00U, 0x80U, 0x01U, 0x21U, 0x01U, 0x0BU, 0x20U, 0x00U, 0x20U, + 0x01U, 0x41U, 0x01U, 0x6BU, 0x22U, 0x01U, 0x3AU, 0x00U, 0x80U, 0x01U, 0x20U, + 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x00U, 0x20U, 0x01U, 0x41U, 0xFFU, + 0x01U, 0x71U, 0x22U, 0x01U, 0x1BU, 0x20U, 0x01U, 0x41U, 0x00U, 0x47U, 0x20U, + 0x00U, 0x41U, 0x90U, 0x09U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, 0x42U, 0x7FU, + 0x55U, 0x0DU, 0x00U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xD4U, 0x02U, + 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x00U, 0x3AU, 0x00U, + 0x8FU, 0x09U, 0x20U, 0x00U, 0x29U, 0x03U, 0xB0U, 0x09U, 0x21U, 0x0BU, 0x20U, + 0x00U, 0x41U, 0xC3U, 0x00U, 0x3AU, 0x00U, 0xB0U, 0x09U, 0x20U, 0x00U, 0x20U, + 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x3AU, 0x00U, 0xB3U, 0x09U, 0x20U, 0x00U, + 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x3AU, 0x00U, 0xB2U, 0x09U, 0x20U, + 0x00U, 0x20U, 0x04U, 0x3AU, 0x00U, 0xB1U, 0x09U, 0x20U, 0x00U, 0x41U, 0x8FU, + 0x09U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, + 0x6AU, 0x22U, 0x06U, 0x41U, 0x20U, 0x10U, 0x09U, 0x1AU, 0x20U, 0x00U, 0x20U, + 0x00U, 0x2DU, 0x00U, 0x8FU, 0x09U, 0x41U, 0x01U, 0x6AU, 0x3AU, 0x00U, 0x8FU, + 0x09U, 0x20U, 0x01U, 0x41U, 0x01U, 0x20U, 0x06U, 0x41U, 0x20U, 0x10U, 0x0CU, + 0x42U, 0x00U, 0x57U, 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, + 0xE4U, 0x02U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x20U, 0x0BU, 0x37U, + 0x03U, 0xB0U, 0x09U, 0x41U, 0xB5U, 0x13U, 0x41U, 0x0FU, 0x20U, 0x07U, 0xADU, + 0x22U, 0x0FU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xC5U, 0x13U, 0x41U, 0x05U, 0x20U, + 0x00U, 0x31U, 0x00U, 0x8FU, 0x09U, 0x10U, 0x03U, 0x1AU, 0x41U, 0xCEU, 0x0AU, + 0x41U, 0x0CU, 0x20U, 0x0CU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xCBU, 0x13U, 0x41U, + 0x06U, 0x20U, 0x00U, 0x41U, 0xEEU, 0x09U, 0x6AU, 0x41U, 0x02U, 0x41U, 0x01U, + 0x10U, 0x08U, 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x41U, 0x02U, + 0x47U, 0x20U, 0x0CU, 0xB9U, 0x22U, 0x10U, 0x44U, 0x52U, 0xB8U, 0x1EU, 0x85U, + 0xEBU, 0x51U, 0xE0U, 0x3FU, 0xA2U, 0x22U, 0x11U, 0x99U, 0x44U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0xE0U, 0x43U, 0x63U, 0x04U, 0x7EU, 0x20U, 0x11U, + 0xB0U, 0x05U, 0x42U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, + 0x80U, 0x7FU, 0x0BU, 0x21U, 0x0BU, 0x20U, 0x02U, 0x41U, 0x7FU, 0x73U, 0x71U, + 0x21U, 0x01U, 0x20U, 0x10U, 0x44U, 0x9AU, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, + 0xE9U, 0x3FU, 0xA2U, 0x22U, 0x10U, 0x99U, 0x44U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0xE0U, 0x43U, 0x63U, 0x04U, 0x7EU, 0x20U, 0x10U, 0xB0U, 0x05U, 0x42U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x7FU, - 0x0BU, 0x22U, 0x0AU, 0x42U, 0x02U, 0x20U, 0x0AU, 0x42U, 0x02U, 0x55U, 0x1BU, - 0x20U, 0x0BU, 0x20U, 0x02U, 0x41U, 0xD3U, 0x00U, 0x46U, 0x1BU, 0x59U, 0x0DU, - 0x01U, 0x41U, 0xC6U, 0x0FU, 0x41U, 0x39U, 0x42U, 0x83U, 0x03U, 0x10U, 0x05U, - 0x1AU, 0x05U, 0x20U, 0x00U, 0x31U, 0x00U, 0x8FU, 0x09U, 0x20U, 0x0FU, 0x44U, - 0x52U, 0xB8U, 0x1EU, 0x85U, 0xEBU, 0x51U, 0xE0U, 0x3FU, 0xA2U, 0x22U, 0x0FU, - 0x99U, 0x44U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0xE0U, 0x43U, 0x63U, - 0x04U, 0x7EU, 0x20U, 0x0FU, 0xB0U, 0x05U, 0x42U, 0x80U, 0x80U, 0x80U, 0x80U, - 0x80U, 0x80U, 0x80U, 0x80U, 0x80U, 0x7FU, 0x0BU, 0x22U, 0x0AU, 0x42U, 0x02U, - 0x20U, 0x0AU, 0x42U, 0x02U, 0x55U, 0x1BU, 0x5AU, 0x0DU, 0x01U, 0x41U, 0xBCU, - 0x16U, 0x41U, 0x36U, 0x42U, 0x88U, 0x03U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, - 0x41U, 0xC5U, 0x19U, 0x41U, 0x11U, 0x41U, 0xC2U, 0x08U, 0x41U, 0x10U, 0x41U, - 0x00U, 0x10U, 0x08U, 0x1AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xECU, 0x09U, 0x41U, - 0x01U, 0x47U, 0x20U, 0x04U, 0x72U, 0x45U, 0x04U, 0x40U, 0x10U, 0x0DU, 0x21U, - 0x0AU, 0x20U, 0x00U, 0x41U, 0x9EU, 0x01U, 0x6AU, 0x22U, 0x07U, 0x42U, 0x80U, - 0x80U, 0x80U, 0x80U, 0xB0U, 0x8EU, 0xC0U, 0xC0U, 0x14U, 0x37U, 0x00U, 0x00U, - 0x20U, 0x00U, 0x41U, 0xE8U, 0x80U, 0x01U, 0x3BU, 0x00U, 0x99U, 0x01U, 0x20U, - 0x00U, 0x41U, 0xA0U, 0x36U, 0x3BU, 0x00U, 0x93U, 0x01U, 0x20U, 0x00U, 0x41U, - 0xA0U, 0x34U, 0x3BU, 0x00U, 0x8DU, 0x01U, 0x20U, 0x00U, 0x41U, 0x00U, 0x36U, - 0x00U, 0x89U, 0x01U, 0x20U, 0x00U, 0x41U, 0x24U, 0x3AU, 0x00U, 0x88U, 0x01U, - 0x20U, 0x00U, 0x42U, 0x92U, 0x80U, 0x8CU, 0x93U, 0x82U, 0x10U, 0x37U, 0x03U, - 0x80U, 0x01U, 0x20U, 0x00U, 0x41U, 0x00U, 0x36U, 0x00U, 0x9BU, 0x01U, 0x20U, - 0x00U, 0x20U, 0x0AU, 0xA7U, 0x22U, 0x04U, 0x41U, 0x05U, 0x6AU, 0x22U, 0x01U, - 0x3AU, 0x00U, 0x98U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x08U, 0x76U, - 0x3AU, 0x00U, 0x97U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x10U, 0x76U, - 0x3AU, 0x00U, 0x96U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x18U, 0x76U, - 0x3AU, 0x00U, 0x95U, 0x01U, 0x20U, 0x00U, 0x20U, 0x04U, 0x41U, 0x01U, 0x6AU, - 0x22U, 0x01U, 0x3AU, 0x00U, 0x92U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x08U, 0x76U, 0x3AU, 0x00U, 0x91U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x10U, 0x76U, 0x3AU, 0x00U, 0x90U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x18U, 0x76U, 0x3AU, 0x00U, 0x8FU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, - 0x03U, 0xFCU, 0x09U, 0x37U, 0x03U, 0xA6U, 0x01U, 0x20U, 0x00U, 0x41U, 0x83U, - 0x29U, 0x3BU, 0x01U, 0xBAU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, 0x03U, - 0x84U, 0x0AU, 0x37U, 0x03U, 0xAEU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x28U, - 0x02U, 0x8CU, 0x0AU, 0x36U, 0x02U, 0xB6U, 0x01U, 0x20U, 0x00U, 0x41U, 0xB0U, - 0x1AU, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0xBCU, 0x01U, 0x20U, 0x00U, 0x41U, - 0xB8U, 0x1AU, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0xC4U, 0x01U, 0x20U, 0x00U, - 0x41U, 0xC0U, 0x1AU, 0x28U, 0x02U, 0x00U, 0x36U, 0x02U, 0xCCU, 0x01U, 0x20U, - 0x00U, 0x41U, 0xD0U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x80U, 0x04U, 0x10U, - 0x0EU, 0xA7U, 0x20U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x41U, 0xF0U, 0xB2U, 0x08U, - 0x36U, 0x02U, 0x08U, 0x20U, 0x01U, 0x41U, 0x0BU, 0x6AU, 0x20U, 0x02U, 0x3AU, - 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, 0x16U, 0x6AU, 0x20U, 0x03U, 0x3AU, 0x00U, - 0x00U, 0x20U, 0x01U, 0x41U, 0x15U, 0x6AU, 0x41U, 0x19U, 0x3AU, 0x00U, 0x00U, - 0x20U, 0x01U, 0x41U, 0x0CU, 0x6AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, - 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x42U, 0xF0U, 0xA7U, 0x80U, 0xBFU, 0x81U, - 0x8EU, 0xC6U, 0x80U, 0xD4U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x0DU, 0x6AU, 0x42U, 0xE1U, 0xC1U, 0xDFU, 0x80U, 0x87U, 0xA3U, 0x80U, 0xABU, - 0xF0U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x17U, 0x6AU, 0x22U, - 0x04U, 0x20U, 0x05U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, - 0x41U, 0x1FU, 0x6AU, 0x20U, 0x05U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x00U, - 0x20U, 0x01U, 0x41U, 0x27U, 0x6AU, 0x20U, 0x05U, 0x41U, 0x10U, 0x6AU, 0x29U, - 0x03U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x2FU, 0x6AU, 0x20U, - 0x05U, 0x41U, 0x18U, 0x6AU, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, - 0x03U, 0x20U, 0x04U, 0x6AU, 0x22U, 0x01U, 0x41U, 0xE1U, 0xE3U, 0x03U, 0x3BU, - 0x01U, 0x00U, 0x20U, 0x01U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x22U, - 0x01U, 0x6BU, 0x21U, 0x04U, 0x20U, 0x07U, 0x20U, 0x01U, 0x20U, 0x04U, 0x10U, - 0x0FU, 0x22U, 0x0AU, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0x00U, 0x20U, 0x00U, - 0x20U, 0x0AU, 0x3CU, 0x00U, 0xA1U, 0x01U, 0x20U, 0x00U, 0x41U, 0xE8U, 0x00U, - 0x3AU, 0x00U, 0x99U, 0x01U, 0x20U, 0x00U, 0x20U, 0x0AU, 0x42U, 0x08U, 0x88U, - 0x3CU, 0x00U, 0xA0U, 0x01U, 0x20U, 0x00U, 0x20U, 0x0AU, 0x42U, 0x10U, 0x88U, - 0x3CU, 0x00U, 0x9FU, 0x01U, 0x20U, 0x00U, 0x20U, 0x0AU, 0x42U, 0x20U, 0x88U, - 0x3CU, 0x00U, 0x9DU, 0x01U, 0x20U, 0x00U, 0x20U, 0x0AU, 0x42U, 0x28U, 0x88U, - 0x3CU, 0x00U, 0x9CU, 0x01U, 0x20U, 0x00U, 0x20U, 0x0AU, 0x42U, 0x30U, 0x88U, - 0x3CU, 0x00U, 0x9BU, 0x01U, 0x20U, 0x00U, 0x20U, 0x0AU, 0x42U, 0x38U, 0x88U, - 0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, 0x00U, 0x72U, 0x3AU, 0x00U, 0x9AU, - 0x01U, 0x41U, 0xB1U, 0x0CU, 0x41U, 0x22U, 0x20U, 0x01U, 0x20U, 0x04U, 0x41U, - 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xAAU, 0x08U, 0x41U, 0x18U, 0x20U, 0x00U, - 0x41U, 0x20U, 0x6AU, 0x41U, 0x20U, 0x20U, 0x01U, 0x20U, 0x04U, 0x10U, 0x10U, - 0x22U, 0x0AU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x0AU, 0x42U, 0x20U, 0x51U, 0x04U, - 0x40U, 0x41U, 0xAEU, 0x11U, 0x41U, 0x2AU, 0x42U, 0xD6U, 0x03U, 0x10U, 0x05U, - 0x1AU, 0x0BU, 0x41U, 0x9DU, 0x14U, 0x41U, 0x25U, 0x42U, 0xD8U, 0x03U, 0x10U, - 0x0BU, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, - 0x02U, 0x40U, 0x20U, 0x02U, 0x41U, 0xD2U, 0x00U, 0x6BU, 0x0EU, 0x02U, 0x00U, - 0x03U, 0x01U, 0x0BU, 0x41U, 0xBBU, 0x08U, 0x41U, 0x06U, 0x20U, 0x05U, 0x20U, - 0x03U, 0x20U, 0x00U, 0x41U, 0xEEU, 0x09U, 0x6AU, 0x41U, 0x02U, 0x10U, 0x0CU, - 0x22U, 0x0AU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x0AU, 0x42U, 0x00U, 0x57U, 0x04U, - 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xE2U, 0x03U, 0x10U, 0x0BU, - 0x1AU, 0x0BU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x41U, 0xD2U, 0x00U, - 0x46U, 0x04U, 0x40U, 0x41U, 0x81U, 0x1AU, 0x41U, 0x29U, 0x42U, 0xE4U, 0x03U, - 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xD7U, 0x19U, 0x41U, 0x2AU, 0x42U, 0xE6U, - 0x03U, 0x10U, 0x05U, 0x1AU, 0x0CU, 0x01U, 0x0BU, 0x20U, 0x02U, 0x41U, 0xC8U, - 0x00U, 0x47U, 0x0DU, 0x02U, 0x0BU, 0x20U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, - 0x22U, 0x01U, 0x41U, 0x22U, 0x41U, 0x01U, 0x20U, 0x08U, 0x41U, 0x14U, 0x41U, - 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x11U, 0x1AU, 0x20U, - 0x01U, 0x41U, 0x22U, 0x41U, 0x05U, 0x10U, 0x12U, 0x1AU, 0x41U, 0x05U, 0x41U, - 0x8BU, 0x80U, 0x3CU, 0x41U, 0x06U, 0x10U, 0x13U, 0x1AU, 0x41U, 0x06U, 0x20U, - 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x41U, 0x07U, 0x10U, 0x14U, 0x42U, 0x07U, - 0x51U, 0x04U, 0x40U, 0x02U, 0x40U, 0x41U, 0x07U, 0x41U, 0x9FU, 0x80U, 0x14U, - 0x41U, 0x08U, 0x10U, 0x13U, 0x42U, 0x08U, 0x52U, 0x0DU, 0x00U, 0x20U, 0x00U, - 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x20U, 0x41U, 0x08U, 0x10U, 0x15U, 0x42U, - 0x20U, 0x52U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xFDU, - 0x03U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x29U, 0x03U, 0x80U, 0x01U, - 0x20U, 0x00U, 0x29U, 0x03U, 0xB0U, 0x09U, 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, - 0x88U, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, 0xB8U, 0x09U, 0x52U, 0x72U, 0x20U, - 0x00U, 0x29U, 0x03U, 0x90U, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC0U, 0x09U, - 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, 0x98U, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, - 0xC8U, 0x09U, 0x52U, 0x72U, 0x72U, 0x0DU, 0x00U, 0x41U, 0xC2U, 0x10U, 0x41U, - 0x3DU, 0x42U, 0x81U, 0x04U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0x7FU, - 0x21U, 0x03U, 0x20U, 0x06U, 0x45U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, - 0x41U, 0xD0U, 0x00U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x22U, 0x41U, 0x18U, 0x20U, - 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x22U, 0x03U, 0x41U, 0x20U, 0x41U, 0x00U, - 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x11U, 0x1AU, 0x20U, 0x01U, - 0x41U, 0x22U, 0x41U, 0x09U, 0x10U, 0x12U, 0x42U, 0x09U, 0x51U, 0x0DU, 0x00U, - 0x41U, 0xFFU, 0x0FU, 0x41U, 0xC3U, 0x00U, 0x42U, 0x8CU, 0x04U, 0x10U, 0x0BU, - 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x20U, 0x00U, 0x2DU, - 0x00U, 0xEDU, 0x09U, 0x41U, 0x02U, 0x74U, 0x6AU, 0x20U, 0x03U, 0x36U, 0x02U, - 0x00U, 0x10U, 0x0DU, 0x21U, 0x0AU, 0x20U, 0x00U, 0x41U, 0x14U, 0x10U, 0x07U, - 0x1AU, 0x20U, 0x00U, 0x41U, 0x9EU, 0x01U, 0x6AU, 0x42U, 0x80U, 0x80U, 0x80U, - 0x80U, 0xB0U, 0x8EU, 0xC0U, 0xC0U, 0x14U, 0x37U, 0x00U, 0x00U, 0x20U, 0x00U, - 0x41U, 0xE8U, 0x80U, 0x01U, 0x3BU, 0x00U, 0x99U, 0x01U, 0x20U, 0x00U, 0x41U, - 0xA0U, 0x36U, 0x3BU, 0x00U, 0x93U, 0x01U, 0x20U, 0x00U, 0x41U, 0xA0U, 0x34U, - 0x3BU, 0x00U, 0x8DU, 0x01U, 0x20U, 0x00U, 0x41U, 0x00U, 0x36U, 0x00U, 0x89U, - 0x01U, 0x20U, 0x00U, 0x41U, 0x24U, 0x3AU, 0x00U, 0x88U, 0x01U, 0x20U, 0x00U, - 0x42U, 0x92U, 0x80U, 0xD8U, 0x90U, 0x82U, 0x10U, 0x37U, 0x03U, 0x80U, 0x01U, - 0x20U, 0x00U, 0x41U, 0x00U, 0x36U, 0x00U, 0x9BU, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0xA7U, 0x22U, 0x03U, 0x41U, 0x05U, 0x6AU, 0x22U, 0x01U, 0x3AU, 0x00U, - 0x98U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x08U, 0x76U, 0x3AU, 0x00U, - 0x97U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x10U, 0x76U, 0x3AU, 0x00U, - 0x96U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x18U, 0x76U, 0x3AU, 0x00U, - 0x95U, 0x01U, 0x20U, 0x00U, 0x20U, 0x03U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x01U, - 0x3AU, 0x00U, 0x92U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x08U, 0x76U, - 0x3AU, 0x00U, 0x91U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x10U, 0x76U, - 0x3AU, 0x00U, 0x90U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x18U, 0x76U, - 0x3AU, 0x00U, 0x8FU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, 0x03U, 0x00U, - 0x37U, 0x03U, 0xA6U, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, 0x03U, 0x08U, - 0x37U, 0x03U, 0xAEU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x10U, - 0x36U, 0x02U, 0xB6U, 0x01U, 0x20U, 0x00U, 0x41U, 0xBAU, 0x01U, 0x6AU, 0x22U, - 0x01U, 0x41U, 0xC6U, 0x07U, 0x10U, 0x0EU, 0xA7U, 0x20U, 0x01U, 0x6AU, 0x22U, - 0x01U, 0x41U, 0xFBU, 0xDDU, 0x03U, 0x3BU, 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x02U, 0x6AU, 0x21U, 0x01U, 0x20U, 0x00U, 0x28U, 0x02U, 0x20U, 0x22U, 0x03U, - 0x04U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x00U, 0x20U, 0x01U, - 0x41U, 0x04U, 0x6AU, 0x41U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x06U, 0x6AU, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0x05U, 0x6AU, 0x21U, 0x01U, - 0x20U, 0x03U, 0x41U, 0x7FU, 0x46U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xFBU, - 0x00U, 0x3BU, 0x00U, 0x00U, 0x20U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x05U, 0x20U, - 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x00U, 0x20U, 0x01U, 0x20U, 0x03U, - 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x02U, 0x20U, 0x01U, 0x20U, 0x03U, 0x29U, - 0x03U, 0x08U, 0x37U, 0x03U, 0x0AU, 0x20U, 0x01U, 0x20U, 0x03U, 0x29U, 0x03U, - 0x10U, 0x37U, 0x03U, 0x12U, 0x20U, 0x01U, 0x20U, 0x03U, 0x29U, 0x03U, 0x18U, - 0x37U, 0x03U, 0x1AU, 0x20U, 0x02U, 0x41U, 0x21U, 0x6AU, 0x0BU, 0x21U, 0x01U, - 0x0BU, 0x20U, 0x01U, 0x41U, 0xE1U, 0xDDU, 0x03U, 0x3BU, 0x00U, 0x00U, 0x20U, - 0x01U, 0x41U, 0x02U, 0x6AU, 0x21U, 0x03U, 0x20U, 0x00U, 0x28U, 0x02U, 0x24U, - 0x22U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, - 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, - 0x46U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, - 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, - 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, - 0x03U, 0x09U, 0x20U, 0x01U, 0x41U, 0x11U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, - 0x08U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x19U, 0x6AU, 0x20U, 0x02U, - 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x21U, 0x6AU, - 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x29U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x03U, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, - 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x28U, 0x21U, 0x02U, - 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x01U, 0x41U, - 0x02U, 0x6AU, 0x21U, 0x03U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, - 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, - 0x20U, 0x02U, 0x41U, 0x7FU, 0x46U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xFBU, - 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x05U, 0x20U, - 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, - 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x41U, 0x11U, 0x6AU, - 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x19U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x00U, 0x20U, - 0x01U, 0x41U, 0x21U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, - 0x00U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x03U, 0x0BU, - 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, - 0x02U, 0x2CU, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, - 0x01U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x21U, 0x03U, 0x20U, 0x02U, 0x04U, - 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, - 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x46U, 0x04U, 0x7FU, - 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, - 0x09U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, - 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, - 0x01U, 0x41U, 0x11U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, - 0x00U, 0x20U, 0x01U, 0x41U, 0x19U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, - 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x21U, 0x6AU, 0x20U, 0x02U, 0x29U, - 0x03U, 0x18U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x0BU, - 0x05U, 0x20U, 0x03U, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, - 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x30U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, - 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x21U, - 0x03U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, - 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, - 0x7FU, 0x46U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, - 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xD0U, - 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, - 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x41U, 0x11U, 0x6AU, 0x20U, 0x02U, 0x29U, - 0x03U, 0x08U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x19U, 0x6AU, 0x20U, - 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x21U, - 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, - 0x41U, 0x29U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x03U, 0x0BU, 0x22U, 0x01U, 0x41U, - 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x34U, 0x21U, - 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x01U, - 0x41U, 0x02U, 0x6AU, 0x21U, 0x03U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, - 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, - 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x46U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, - 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x05U, - 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, - 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x41U, 0x11U, - 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, - 0x41U, 0x19U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x00U, - 0x20U, 0x01U, 0x41U, 0x21U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, - 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x03U, - 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, - 0x28U, 0x02U, 0x38U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, - 0x00U, 0x01U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x21U, 0x03U, 0x20U, 0x02U, - 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, - 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x46U, 0x04U, - 0x7FU, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, - 0x41U, 0x09U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, - 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, - 0x20U, 0x01U, 0x41U, 0x11U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, - 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x19U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, - 0x10U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x21U, 0x6AU, 0x20U, 0x02U, - 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, - 0x0BU, 0x05U, 0x20U, 0x03U, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, - 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x3CU, 0x21U, 0x02U, 0x20U, 0x01U, - 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, - 0x21U, 0x03U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, - 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, - 0x41U, 0x7FU, 0x46U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, - 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, - 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, - 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x41U, 0x11U, 0x6AU, 0x20U, 0x02U, - 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x19U, 0x6AU, - 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, - 0x21U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x00U, 0x20U, - 0x01U, 0x41U, 0x29U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x03U, 0x0BU, 0x22U, 0x01U, - 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x40U, + 0x0BU, 0x21U, 0x0DU, 0x20U, 0x00U, 0x31U, 0x00U, 0x8FU, 0x09U, 0x21U, 0x0EU, + 0x02U, 0x40U, 0x20U, 0x01U, 0x04U, 0x40U, 0x20U, 0x0EU, 0x20U, 0x0BU, 0x42U, + 0x02U, 0x20U, 0x0BU, 0x42U, 0x02U, 0x55U, 0x1BU, 0x59U, 0x0DU, 0x01U, 0x41U, + 0x8AU, 0x14U, 0x41U, 0x36U, 0x42U, 0x88U, 0x03U, 0x10U, 0x05U, 0x1AU, 0x05U, + 0x20U, 0x0EU, 0x20U, 0x0DU, 0x42U, 0x02U, 0x20U, 0x0DU, 0x42U, 0x02U, 0x55U, + 0x1BU, 0x20U, 0x0CU, 0x20U, 0x04U, 0x41U, 0xD3U, 0x00U, 0x46U, 0x1BU, 0x59U, + 0x0DU, 0x01U, 0x41U, 0xD1U, 0x13U, 0x41U, 0x39U, 0x42U, 0x83U, 0x03U, 0x10U, + 0x05U, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0xC0U, 0x14U, 0x41U, 0x11U, 0x41U, 0xD2U, + 0x14U, 0x41U, 0x10U, 0x41U, 0x00U, 0x10U, 0x08U, 0x1AU, 0x20U, 0x00U, 0x2DU, + 0x00U, 0xECU, 0x09U, 0x41U, 0x01U, 0x47U, 0x20U, 0x02U, 0x72U, 0x45U, 0x04U, + 0x40U, 0x10U, 0x0DU, 0x21U, 0x0BU, 0x20U, 0x00U, 0x41U, 0xE8U, 0x80U, 0x01U, + 0x3BU, 0x00U, 0x99U, 0x01U, 0x20U, 0x00U, 0x41U, 0xA0U, 0x36U, 0x3BU, 0x00U, + 0x93U, 0x01U, 0x20U, 0x00U, 0x41U, 0xA0U, 0x34U, 0x3BU, 0x00U, 0x8DU, 0x01U, + 0x20U, 0x00U, 0x41U, 0x00U, 0x36U, 0x00U, 0x89U, 0x01U, 0x20U, 0x00U, 0x41U, + 0x24U, 0x3AU, 0x00U, 0x88U, 0x01U, 0x20U, 0x00U, 0x42U, 0x92U, 0x80U, 0x8CU, + 0x93U, 0x82U, 0x10U, 0x37U, 0x03U, 0x80U, 0x01U, 0x20U, 0x00U, 0x41U, 0x00U, + 0x36U, 0x00U, 0x9BU, 0x01U, 0x20U, 0x00U, 0x42U, 0x80U, 0x80U, 0x80U, 0x80U, + 0xB0U, 0x8EU, 0xC0U, 0xC0U, 0x14U, 0x37U, 0x00U, 0x9EU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0xA7U, 0x22U, 0x02U, 0x41U, 0x05U, 0x6AU, 0x22U, 0x01U, 0x3AU, + 0x00U, 0x98U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x08U, 0x76U, 0x3AU, + 0x00U, 0x97U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x10U, 0x76U, 0x3AU, + 0x00U, 0x96U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x18U, 0x76U, 0x3AU, + 0x00U, 0x95U, 0x01U, 0x20U, 0x00U, 0x20U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x22U, + 0x01U, 0x3AU, 0x00U, 0x92U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x08U, + 0x76U, 0x3AU, 0x00U, 0x91U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x10U, + 0x76U, 0x3AU, 0x00U, 0x90U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x18U, + 0x76U, 0x3AU, 0x00U, 0x8FU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, 0x03U, + 0xFCU, 0x09U, 0x37U, 0x03U, 0xA6U, 0x01U, 0x20U, 0x00U, 0x41U, 0x83U, 0x29U, + 0x3BU, 0x01U, 0xBAU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, 0x03U, 0x84U, + 0x0AU, 0x37U, 0x03U, 0xAEU, 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, + 0x8CU, 0x0AU, 0x36U, 0x02U, 0xB6U, 0x01U, 0x20U, 0x00U, 0x41U, 0x80U, 0x08U, + 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0xBCU, 0x01U, 0x20U, 0x00U, 0x41U, 0x88U, + 0x08U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0xC4U, 0x01U, 0x20U, 0x00U, 0x41U, + 0x90U, 0x08U, 0x28U, 0x02U, 0x00U, 0x36U, 0x02U, 0xCCU, 0x01U, 0x20U, 0x00U, + 0x41U, 0xD0U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x80U, 0x04U, 0x10U, 0x0EU, + 0xA7U, 0x20U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x41U, 0xF0U, 0xB2U, 0x08U, 0x36U, + 0x02U, 0x08U, 0x20U, 0x01U, 0x41U, 0x0BU, 0x6AU, 0x20U, 0x04U, 0x3AU, 0x00U, + 0x00U, 0x20U, 0x01U, 0x41U, 0x16U, 0x6AU, 0x20U, 0x03U, 0x3AU, 0x00U, 0x00U, + 0x20U, 0x01U, 0x41U, 0x15U, 0x6AU, 0x41U, 0x19U, 0x3AU, 0x00U, 0x00U, 0x20U, + 0x01U, 0x41U, 0x0CU, 0x6AU, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x3AU, + 0x00U, 0x00U, 0x20U, 0x01U, 0x42U, 0xF0U, 0xA7U, 0x80U, 0xBFU, 0x81U, 0x8EU, + 0xC6U, 0x80U, 0xD4U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x0DU, + 0x6AU, 0x42U, 0xE1U, 0xC1U, 0xDFU, 0x80U, 0x87U, 0xA3U, 0x80U, 0xABU, 0xF0U, + 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x17U, 0x6AU, 0x22U, 0x02U, + 0x20U, 0x05U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, + 0x1FU, 0x6AU, 0x20U, 0x05U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x00U, 0x20U, + 0x01U, 0x41U, 0x27U, 0x6AU, 0x20U, 0x05U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, + 0x00U, 0x20U, 0x01U, 0x41U, 0x2FU, 0x6AU, 0x20U, 0x05U, 0x29U, 0x03U, 0x18U, + 0x37U, 0x03U, 0x00U, 0x20U, 0x02U, 0x20U, 0x03U, 0x6AU, 0x22U, 0x01U, 0x41U, + 0xE1U, 0xE3U, 0x03U, 0x3BU, 0x01U, 0x00U, 0x20U, 0x01U, 0x20U, 0x00U, 0x41U, + 0x80U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x6BU, 0x21U, 0x02U, 0x20U, 0x00U, 0x20U, + 0x01U, 0x20U, 0x02U, 0x10U, 0x0FU, 0x22U, 0x0BU, 0x3CU, 0x00U, 0xA1U, 0x01U, + 0x20U, 0x00U, 0x41U, 0xE8U, 0x00U, 0x3AU, 0x00U, 0x99U, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xA0U, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x10U, 0x88U, 0x3CU, 0x00U, 0x9FU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0x9EU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x20U, 0x88U, 0x3CU, 0x00U, 0x9DU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x28U, 0x88U, 0x3CU, 0x00U, 0x9CU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x30U, 0x88U, 0x3CU, 0x00U, 0x9BU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x38U, 0x88U, 0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, + 0x00U, 0x72U, 0x3AU, 0x00U, 0x9AU, 0x01U, 0x41U, 0xE2U, 0x14U, 0x41U, 0x22U, + 0x20U, 0x01U, 0x20U, 0x02U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0x84U, + 0x15U, 0x41U, 0x18U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x20U, 0x20U, + 0x01U, 0x20U, 0x02U, 0x10U, 0x10U, 0x22U, 0x0BU, 0x10U, 0x03U, 0x1AU, 0x20U, + 0x0BU, 0x42U, 0x20U, 0x51U, 0x04U, 0x40U, 0x41U, 0x9CU, 0x15U, 0x41U, 0x2AU, + 0x42U, 0xD6U, 0x03U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xC6U, 0x15U, 0x41U, + 0x25U, 0x42U, 0xD8U, 0x03U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x02U, + 0x40U, 0x20U, 0x04U, 0x41U, 0xD2U, 0x00U, 0x6BU, 0x22U, 0x01U, 0x41U, 0x01U, + 0x4DU, 0x04U, 0x40U, 0x20U, 0x01U, 0x41U, 0x01U, 0x46U, 0x0DU, 0x01U, 0x41U, + 0xEBU, 0x15U, 0x41U, 0x06U, 0x20U, 0x05U, 0x20U, 0x03U, 0x20U, 0x00U, 0x41U, + 0xEEU, 0x09U, 0x6AU, 0x41U, 0x02U, 0x10U, 0x0CU, 0x22U, 0x0BU, 0x10U, 0x03U, + 0x1AU, 0x20U, 0x0BU, 0x42U, 0x00U, 0x57U, 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, + 0x41U, 0x1AU, 0x42U, 0xE2U, 0x03U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, + 0x2DU, 0x00U, 0xEDU, 0x09U, 0x41U, 0xD2U, 0x00U, 0x46U, 0x04U, 0x40U, 0x41U, + 0xF2U, 0x15U, 0x41U, 0x29U, 0x42U, 0xE4U, 0x03U, 0x10U, 0x05U, 0x1AU, 0x0BU, + 0x41U, 0x9BU, 0x16U, 0x41U, 0x2AU, 0x42U, 0xE6U, 0x03U, 0x10U, 0x05U, 0x1AU, + 0x05U, 0x20U, 0x04U, 0x41U, 0xC8U, 0x00U, 0x47U, 0x0DU, 0x02U, 0x0BU, 0x20U, + 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x22U, 0x41U, 0x01U, + 0x20U, 0x08U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, + 0x00U, 0x10U, 0x11U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x05U, 0x10U, + 0x12U, 0x1AU, 0x41U, 0x05U, 0x41U, 0x8BU, 0x80U, 0x3CU, 0x41U, 0x06U, 0x10U, + 0x13U, 0x1AU, 0x41U, 0x06U, 0x20U, 0x00U, 0x2DU, 0x00U, 0xEDU, 0x09U, 0x41U, + 0x07U, 0x10U, 0x14U, 0x42U, 0x07U, 0x51U, 0x04U, 0x40U, 0x02U, 0x40U, 0x41U, + 0x07U, 0x41U, 0x9FU, 0x80U, 0x14U, 0x41U, 0x08U, 0x10U, 0x13U, 0x42U, 0x08U, + 0x52U, 0x0DU, 0x00U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x20U, + 0x41U, 0x08U, 0x10U, 0x15U, 0x42U, 0x20U, 0x52U, 0x04U, 0x40U, 0x41U, 0xB4U, + 0x0AU, 0x41U, 0x1AU, 0x42U, 0xFDU, 0x03U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, + 0x00U, 0x29U, 0x03U, 0x80U, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, 0xB0U, 0x09U, + 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, 0x88U, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, + 0xB8U, 0x09U, 0x52U, 0x72U, 0x20U, 0x00U, 0x29U, 0x03U, 0x90U, 0x01U, 0x20U, + 0x00U, 0x29U, 0x03U, 0xC0U, 0x09U, 0x52U, 0x20U, 0x00U, 0x29U, 0x03U, 0x98U, + 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC8U, 0x09U, 0x52U, 0x72U, 0x72U, 0x0DU, + 0x00U, 0x41U, 0xC5U, 0x16U, 0x41U, 0x3DU, 0x42U, 0x81U, 0x04U, 0x10U, 0x05U, + 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x07U, 0x45U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, + 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x22U, 0x01U, 0x41U, 0x22U, 0x41U, 0x18U, + 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x41U, 0x20U, 0x41U, 0x00U, 0x41U, + 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x11U, 0x1AU, 0x20U, 0x01U, 0x41U, + 0x22U, 0x41U, 0x09U, 0x10U, 0x12U, 0x42U, 0x09U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0x82U, 0x17U, 0x41U, 0xC3U, 0x00U, 0x42U, 0x8CU, 0x04U, 0x10U, 0x0BU, 0x1AU, + 0x0BU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x20U, 0x00U, 0x2DU, 0x00U, + 0xEDU, 0x09U, 0x41U, 0x02U, 0x74U, 0x6AU, 0x41U, 0x7FU, 0x20U, 0x00U, 0x41U, + 0xB0U, 0x09U, 0x6AU, 0x20U, 0x07U, 0x1BU, 0x36U, 0x02U, 0x00U, 0x10U, 0x0DU, + 0x21U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x14U, 0x10U, 0x07U, 0x1AU, 0x20U, 0x00U, + 0x41U, 0x9EU, 0x01U, 0x6AU, 0x42U, 0x80U, 0x80U, 0x80U, 0x80U, 0xB0U, 0x8EU, + 0xC0U, 0xC0U, 0x14U, 0x37U, 0x00U, 0x00U, 0x20U, 0x00U, 0x41U, 0xE8U, 0x80U, + 0x01U, 0x3BU, 0x00U, 0x99U, 0x01U, 0x20U, 0x00U, 0x41U, 0xA0U, 0x36U, 0x3BU, + 0x00U, 0x93U, 0x01U, 0x20U, 0x00U, 0x41U, 0xA0U, 0x34U, 0x3BU, 0x00U, 0x8DU, + 0x01U, 0x20U, 0x00U, 0x41U, 0x00U, 0x36U, 0x00U, 0x89U, 0x01U, 0x20U, 0x00U, + 0x41U, 0x24U, 0x3AU, 0x00U, 0x88U, 0x01U, 0x20U, 0x00U, 0x42U, 0x92U, 0x80U, + 0xD8U, 0x90U, 0x82U, 0x10U, 0x37U, 0x03U, 0x80U, 0x01U, 0x20U, 0x00U, 0x41U, + 0x00U, 0x36U, 0x00U, 0x9BU, 0x01U, 0x20U, 0x00U, 0x20U, 0x0BU, 0xA7U, 0x22U, + 0x02U, 0x41U, 0x05U, 0x6AU, 0x22U, 0x01U, 0x3AU, 0x00U, 0x98U, 0x01U, 0x20U, + 0x00U, 0x20U, 0x01U, 0x41U, 0x08U, 0x76U, 0x3AU, 0x00U, 0x97U, 0x01U, 0x20U, + 0x00U, 0x20U, 0x01U, 0x41U, 0x10U, 0x76U, 0x3AU, 0x00U, 0x96U, 0x01U, 0x20U, + 0x00U, 0x20U, 0x01U, 0x41U, 0x18U, 0x76U, 0x3AU, 0x00U, 0x95U, 0x01U, 0x20U, + 0x00U, 0x20U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x3AU, 0x00U, 0x92U, + 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x08U, 0x76U, 0x3AU, 0x00U, 0x91U, + 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x10U, 0x76U, 0x3AU, 0x00U, 0x90U, + 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x18U, 0x76U, 0x3AU, 0x00U, 0x8FU, + 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0xA6U, + 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0xAEU, + 0x01U, 0x20U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x10U, 0x36U, 0x02U, 0xB6U, + 0x01U, 0x20U, 0x00U, 0x41U, 0xBAU, 0x01U, 0x6AU, 0x22U, 0x01U, 0x41U, 0xC6U, + 0x07U, 0x10U, 0x0EU, 0xA7U, 0x20U, 0x01U, 0x6AU, 0x22U, 0x01U, 0x41U, 0xFBU, + 0xDDU, 0x03U, 0x3BU, 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x21U, + 0x01U, 0x20U, 0x00U, 0x28U, 0x02U, 0x20U, 0x22U, 0x02U, 0x04U, 0x40U, 0x20U, + 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, 0x04U, 0x6AU, + 0x41U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, 0x06U, 0x6AU, 0x21U, + 0x04U, 0x20U, 0x01U, 0x41U, 0x05U, 0x6AU, 0x21U, 0x01U, 0x20U, 0x02U, 0x41U, + 0x7FU, 0x47U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, + 0x00U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x02U, + 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x0AU, 0x20U, + 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x12U, 0x20U, 0x01U, + 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x1AU, 0x20U, 0x04U, 0x41U, + 0x21U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x00U, + 0x20U, 0x04U, 0x41U, 0x01U, 0x6AU, 0x0BU, 0x21U, 0x01U, 0x0BU, 0x20U, 0x01U, + 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x24U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, - 0x01U, 0x41U, 0x02U, 0x6AU, 0x21U, 0x03U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, - 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, - 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x46U, 0x04U, 0x7FU, 0x20U, 0x01U, + 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, + 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x47U, + 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, + 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, + 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x11U, 0x20U, 0x01U, 0x20U, + 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x19U, 0x20U, 0x01U, 0x20U, 0x02U, + 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x21U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, + 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, + 0x41U, 0x09U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x0BU, + 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, + 0x02U, 0x28U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, + 0x01U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, + 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, + 0x7FU, 0x47U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, + 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, + 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x11U, 0x20U, + 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x19U, 0x20U, 0x01U, + 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x21U, 0x20U, 0x01U, 0x41U, + 0x29U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, + 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x01U, 0x41U, 0x02U, + 0x6AU, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, + 0x00U, 0x28U, 0x02U, 0x2CU, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, + 0x3AU, 0x00U, 0x01U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, + 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, + 0x02U, 0x41U, 0x7FU, 0x47U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, + 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, + 0x03U, 0x09U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, + 0x11U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x19U, + 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x21U, 0x20U, + 0x01U, 0x41U, 0x29U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, + 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x01U, + 0x41U, 0x02U, 0x6AU, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, + 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x30U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, + 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, + 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, + 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x47U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, + 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, + 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, + 0x37U, 0x03U, 0x11U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, + 0x03U, 0x19U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, + 0x21U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, + 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x0BU, 0x05U, + 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, + 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x34U, 0x21U, 0x02U, 0x20U, + 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x02U, 0x04U, 0x7FU, + 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, + 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x47U, 0x04U, 0x7FU, 0x20U, + 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, + 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, + 0x03U, 0x08U, 0x37U, 0x03U, 0x11U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, + 0x10U, 0x37U, 0x03U, 0x19U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, + 0x37U, 0x03U, 0x21U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, - 0x05U, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, - 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x41U, - 0x11U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x00U, 0x20U, - 0x01U, 0x41U, 0x19U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, - 0x00U, 0x20U, 0x01U, 0x41U, 0x21U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, - 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x0BU, 0x05U, 0x20U, - 0x03U, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, - 0x00U, 0x28U, 0x02U, 0x44U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, - 0x3AU, 0x00U, 0x01U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x21U, 0x03U, 0x20U, - 0x02U, 0x04U, 0x40U, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, - 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x46U, - 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, - 0x01U, 0x41U, 0x09U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, + 0x0BU, 0x05U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x0BU, 0x22U, 0x01U, 0x41U, + 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, 0x38U, 0x21U, + 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x02U, + 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, + 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, 0x47U, 0x04U, + 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, + 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x20U, + 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x11U, 0x20U, 0x01U, 0x20U, 0x02U, + 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x19U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, + 0x03U, 0x18U, 0x37U, 0x03U, 0x21U, 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x05U, + 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, + 0x09U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, 0x0BU, 0x22U, + 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, 0x28U, 0x02U, + 0x3CU, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, 0x00U, 0x01U, + 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, 0x00U, 0x06U, + 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, 0x41U, 0x7FU, + 0x47U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, + 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, 0x09U, 0x20U, + 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x11U, 0x20U, 0x01U, + 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x19U, 0x20U, 0x01U, 0x20U, + 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x21U, 0x20U, 0x01U, 0x41U, 0x29U, + 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, 0x07U, 0x20U, + 0x01U, 0x41U, 0x09U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x01U, 0x41U, 0x02U, 0x6AU, + 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x00U, + 0x28U, 0x02U, 0x40U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, 0x01U, 0x3AU, + 0x00U, 0x01U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x01U, 0x3AU, + 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, 0x20U, 0x02U, + 0x41U, 0x7FU, 0x47U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, - 0x09U, 0x20U, 0x01U, 0x41U, 0x11U, 0x6AU, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, - 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x19U, 0x6AU, 0x20U, 0x02U, 0x29U, - 0x03U, 0x10U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x21U, 0x6AU, 0x20U, - 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x29U, - 0x6AU, 0x0BU, 0x21U, 0x03U, 0x0BU, 0x20U, 0x03U, 0x41U, 0xE1U, 0xE3U, 0x03U, - 0x3BU, 0x00U, 0x00U, 0x20U, 0x00U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, - 0x22U, 0x01U, 0x20U, 0x03U, 0x20U, 0x00U, 0x6BU, 0x41U, 0xFEU, 0x00U, 0x6BU, - 0x22U, 0x03U, 0x10U, 0x0FU, 0x22U, 0x0AU, 0x3CU, 0x00U, 0xA1U, 0x01U, 0x20U, - 0x00U, 0x41U, 0xE8U, 0x00U, 0x3AU, 0x00U, 0x99U, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xA0U, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x42U, 0x10U, 0x88U, 0x3CU, 0x00U, 0x9FU, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0x9EU, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x42U, 0x20U, 0x88U, 0x3CU, 0x00U, 0x9DU, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x42U, 0x28U, 0x88U, 0x3CU, 0x00U, 0x9CU, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x42U, 0x30U, 0x88U, 0x3CU, 0x00U, 0x9BU, 0x01U, 0x20U, 0x00U, 0x20U, - 0x0AU, 0x42U, 0x38U, 0x88U, 0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, 0x00U, - 0x72U, 0x3AU, 0x00U, 0x9AU, 0x01U, 0x41U, 0xFDU, 0x08U, 0x41U, 0x0BU, 0x20U, - 0x01U, 0x20U, 0x03U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0x9EU, 0x08U, - 0x41U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x20U, 0x01U, 0x20U, 0x03U, 0x10U, - 0x10U, 0x22U, 0x0AU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x0AU, 0x42U, 0x20U, 0x52U, - 0x04U, 0x40U, 0x41U, 0xFFU, 0x10U, 0x41U, 0x2FU, 0x42U, 0xA7U, 0x04U, 0x10U, - 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x8AU, 0x09U, 0x41U, 0x0FU, 0x20U, 0x00U, 0x41U, - 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xE8U, 0x13U, 0x41U, 0x1BU, - 0x42U, 0xAAU, 0x04U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0x80U, - 0x01U, 0x6AU, 0x41U, 0x0CU, 0x72U, 0x22U, 0x05U, 0x41U, 0x14U, 0x20U, 0x00U, - 0x41U, 0xEDU, 0x09U, 0x6AU, 0x41U, 0x01U, 0x10U, 0x09U, 0x22U, 0x0AU, 0x42U, - 0x14U, 0x51U, 0x04U, 0x40U, 0x41U, 0x9DU, 0x0CU, 0x41U, 0x14U, 0x20U, 0x00U, - 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, - 0x0BU, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x41U, 0x0CU, 0x72U, 0x21U, - 0x02U, 0x20U, 0x00U, 0x29U, 0x03U, 0x8CU, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, - 0xBCU, 0x09U, 0x51U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x28U, 0x02U, - 0x9CU, 0x01U, 0x20U, 0x00U, 0x28U, 0x02U, 0xCCU, 0x09U, 0x47U, 0x20U, 0x00U, - 0x29U, 0x03U, 0x94U, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC4U, 0x09U, 0x52U, - 0x72U, 0x0DU, 0x00U, 0x41U, 0xD8U, 0x0EU, 0x41U, 0xCDU, 0x00U, 0x42U, 0xB9U, - 0x04U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, - 0x02U, 0x41U, 0x14U, 0x10U, 0x09U, 0x22U, 0x0CU, 0x42U, 0x3FU, 0x88U, 0x50U, - 0x21U, 0x03U, 0x20U, 0x0CU, 0x42U, 0x00U, 0x59U, 0x04U, 0x40U, 0x41U, 0xFDU, - 0x0CU, 0x41U, 0x30U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x08U, - 0x1AU, 0x0BU, 0x20U, 0x03U, 0x20U, 0x0AU, 0x42U, 0x14U, 0x52U, 0x22U, 0x04U, - 0x41U, 0x02U, 0x74U, 0x41U, 0x02U, 0x41U, 0x00U, 0x20U, 0x06U, 0x1BU, 0x72U, - 0x72U, 0x22U, 0x01U, 0xADU, 0x21U, 0x0EU, 0x20U, 0x01U, 0x41U, 0x03U, 0x71U, - 0x41U, 0x03U, 0x46U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, - 0xC5U, 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0xD2U, 0x08U, 0x41U, 0x02U, - 0x20U, 0x0EU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xCEU, 0x0AU, 0x41U, 0x02U, 0x20U, - 0x04U, 0xADU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xBAU, 0x0AU, 0x41U, 0x02U, 0x20U, - 0x0DU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xCAU, 0x0AU, 0x41U, 0x02U, 0x20U, 0x03U, - 0xADU, 0x10U, 0x03U, 0x1AU, 0x41U, 0x8DU, 0x08U, 0x41U, 0x10U, 0x20U, 0x0AU, - 0x42U, 0x14U, 0x51U, 0xADU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xEDU, 0x08U, 0x41U, - 0x0FU, 0x20U, 0x0DU, 0x10U, 0x03U, 0x1AU, 0x41U, 0x80U, 0x08U, 0x41U, 0x0CU, - 0x42U, 0x7FU, 0x20U, 0x01U, 0x41U, 0x04U, 0x46U, 0xADU, 0x20U, 0x01U, 0x41U, - 0x01U, 0x6BU, 0x41U, 0x02U, 0x49U, 0x1BU, 0x20U, 0x0BU, 0x7CU, 0x22U, 0x0BU, - 0x10U, 0x03U, 0x1AU, 0x20U, 0x0BU, 0x42U, 0x01U, 0x57U, 0x04U, 0x40U, 0x41U, - 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xE7U, 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, - 0x20U, 0x00U, 0x20U, 0x0BU, 0x3CU, 0x00U, 0x20U, 0x20U, 0x00U, 0x41U, 0x20U, - 0x6AU, 0x41U, 0x01U, 0x41U, 0xD5U, 0x0AU, 0x41U, 0x02U, 0x10U, 0x0CU, 0x42U, - 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xEAU, - 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x0CU, 0x42U, 0x00U, 0x59U, 0x04U, - 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x20U, 0x0CU, 0x3CU, 0x00U, 0x20U, 0x41U, - 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x01U, 0x10U, - 0x0CU, 0x50U, 0x45U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, - 0xF4U, 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, - 0x02U, 0x41U, 0x14U, 0x10U, 0x0CU, 0x50U, 0x0DU, 0x00U, 0x41U, 0x83U, 0x14U, - 0x41U, 0x1AU, 0x42U, 0xF7U, 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x20U, - 0x0AU, 0x42U, 0x14U, 0x51U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, - 0xD6U, 0x00U, 0x3AU, 0x00U, 0x80U, 0x01U, 0x41U, 0x01U, 0x21U, 0x04U, 0x03U, - 0x40U, 0x41U, 0x80U, 0x85U, 0x80U, 0x80U, 0x78U, 0x41U, 0x03U, 0x10U, 0x00U, - 0x1AU, 0x20U, 0x04U, 0x41U, 0x03U, 0x46U, 0x45U, 0x04U, 0x40U, 0x41U, 0x00U, - 0x21U, 0x03U, 0x03U, 0x40U, 0x41U, 0x82U, 0x85U, 0x80U, 0x80U, 0x78U, 0x41U, - 0xC3U, 0x00U, 0x10U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x03U, 0x41U, 0x20U, - 0x46U, 0x0DU, 0x00U, 0x41U, 0xD2U, 0x00U, 0x21U, 0x01U, 0x20U, 0x00U, 0x41U, - 0xD2U, 0x00U, 0x41U, 0xC8U, 0x00U, 0x41U, 0xD3U, 0x00U, 0x20U, 0x03U, 0x41U, - 0x0CU, 0x49U, 0x22U, 0x08U, 0x1BU, 0x20U, 0x03U, 0x41U, 0x02U, 0x49U, 0x1BU, - 0x3AU, 0x00U, 0x81U, 0x01U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U, - 0x03U, 0x0EU, 0x02U, 0x02U, 0x00U, 0x01U, 0x0BU, 0x41U, 0xC4U, 0x00U, 0x21U, - 0x01U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0x7EU, 0x41U, 0x74U, 0x20U, 0x08U, 0x1BU, - 0x20U, 0x03U, 0x6AU, 0x21U, 0x01U, 0x0BU, 0x20U, 0x00U, 0x20U, 0x04U, 0x3AU, - 0x00U, 0x83U, 0x01U, 0x20U, 0x00U, 0x20U, 0x01U, 0x3AU, 0x00U, 0x82U, 0x01U, - 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x20U, 0x20U, 0x00U, 0x41U, 0x80U, - 0x01U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x09U, 0x42U, 0x00U, 0x55U, 0x04U, 0x40U, - 0x20U, 0x00U, 0x41U, 0xC3U, 0x00U, 0x3AU, 0x00U, 0x20U, 0x20U, 0x00U, 0x41U, - 0x00U, 0x3AU, 0x00U, 0x50U, 0x20U, 0x00U, 0x20U, 0x04U, 0x3AU, 0x00U, 0x23U, - 0x20U, 0x00U, 0x20U, 0x00U, 0x2FU, 0x00U, 0x81U, 0x01U, 0x3BU, 0x00U, 0x21U, - 0x20U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, - 0x20U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x09U, 0x42U, 0x01U, 0x51U, 0x04U, 0x40U, - 0x20U, 0x00U, 0x2DU, 0x00U, 0x50U, 0x22U, 0x01U, 0x41U, 0x01U, 0x4DU, 0x04U, - 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, - 0x20U, 0x10U, 0x0CU, 0x50U, 0x45U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, - 0x1AU, 0x42U, 0x9BU, 0x05U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0xFFU, 0x09U, - 0x41U, 0x1DU, 0x20U, 0x00U, 0x31U, 0x00U, 0x50U, 0x10U, 0x03U, 0x1AU, 0x05U, - 0x20U, 0x00U, 0x20U, 0x01U, 0x41U, 0x01U, 0x6BU, 0x3AU, 0x00U, 0x50U, 0x20U, - 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x41U, 0x01U, 0x20U, 0x00U, 0x41U, 0x20U, - 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, - 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xA1U, 0x05U, 0x10U, 0x0BU, 0x1AU, 0x0BU, - 0x41U, 0xD5U, 0x08U, 0x41U, 0x18U, 0x20U, 0x00U, 0x31U, 0x00U, 0x50U, 0x10U, - 0x03U, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U, - 0x80U, 0x01U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, 0x50U, 0x45U, 0x04U, 0x40U, - 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xA7U, 0x05U, 0x10U, 0x0BU, 0x1AU, - 0x0BU, 0x41U, 0xECU, 0x09U, 0x41U, 0x13U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, - 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x0BU, 0x20U, 0x03U, 0x41U, - 0x01U, 0x6AU, 0x21U, 0x03U, 0x0CU, 0x01U, 0x0BU, 0x0BU, 0x20U, 0x04U, 0x41U, - 0x01U, 0x6AU, 0x21U, 0x04U, 0x0CU, 0x01U, 0x0BU, 0x0BU, 0x41U, 0x00U, 0x41U, - 0x00U, 0x20U, 0x00U, 0x41U, 0xEDU, 0x09U, 0x6AU, 0x41U, 0x01U, 0x10U, 0x0CU, - 0x50U, 0x45U, 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xAFU, - 0x05U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x05U, - 0x41U, 0x14U, 0x10U, 0x0CU, 0x50U, 0x0DU, 0x00U, 0x41U, 0x83U, 0x14U, 0x41U, - 0x1AU, 0x42U, 0xB2U, 0x05U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x06U, - 0x45U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x02U, 0x41U, 0x14U, 0x20U, 0x00U, - 0x41U, 0xEDU, 0x09U, 0x6AU, 0x41U, 0x01U, 0x10U, 0x0CU, 0x42U, 0x14U, 0x52U, - 0x04U, 0x40U, 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xB9U, 0x05U, 0x10U, - 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0xEDU, 0x09U, 0x6AU, 0x41U, 0x01U, - 0x20U, 0x02U, 0x41U, 0x14U, 0x10U, 0x0CU, 0x42U, 0x01U, 0x51U, 0x0DU, 0x00U, - 0x41U, 0x83U, 0x14U, 0x41U, 0x1AU, 0x42U, 0xBCU, 0x05U, 0x10U, 0x0BU, 0x1AU, - 0x0BU, 0x0BU, 0x41U, 0xC6U, 0x13U, 0x41U, 0x22U, 0x42U, 0xBFU, 0x05U, 0x10U, - 0x05U, 0x1AU, 0x0BU, 0x41U, 0xF9U, 0x0DU, 0x41U, 0x22U, 0x42U, 0xC3U, 0x05U, - 0x10U, 0x0BU, 0x1AU, 0x20U, 0x00U, 0x41U, 0xB0U, 0x0AU, 0x6AU, 0x24U, 0x00U, - 0x20U, 0x0BU, 0x0BU, 0x0BU, 0xCBU, 0x12U, 0x02U, 0x00U, 0x41U, 0x80U, 0x08U, - 0x0BU, 0xA9U, 0x12U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x5FU, 0x63U, - 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x70U, 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, - 0x75U, 0x73U, 0x5FU, 0x70U, 0x72U, 0x65U, 0x73U, 0x65U, 0x6EU, 0x74U, 0x00U, - 0x65U, 0x6DU, 0x69U, 0x74U, 0x5FU, 0x72U, 0x65U, 0x73U, 0x75U, 0x6CU, 0x74U, - 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, - 0x3AU, 0x20U, 0x45U, 0x6DU, 0x69U, 0x74U, 0x20U, 0x72U, 0x65U, 0x73U, 0x75U, - 0x6CU, 0x74U, 0x00U, 0x41U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, 0x6EU, - 0x67U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x00U, 0x6FU, 0x70U, 0x00U, - 0x44U, 0x65U, 0x63U, 0x72U, 0x65U, 0x6DU, 0x65U, 0x6EU, 0x74U, 0x20U, 0x76U, - 0x6FU, 0x74U, 0x65U, 0x20U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x74U, - 0x6FU, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x64U, 0x61U, 0x74U, - 0x61U, 0x5FU, 0x7AU, 0x65U, 0x72U, 0x6FU, 0x00U, 0x45U, 0x6DU, 0x69U, 0x74U, - 0x74U, 0x65U, 0x64U, 0x54U, 0x78U, 0x6EU, 0x00U, 0x6CU, 0x00U, 0x45U, 0x6DU, - 0x69U, 0x74U, 0x74U, 0x65U, 0x64U, 0x54U, 0x78U, 0x6EU, 0x48U, 0x61U, 0x73U, - 0x68U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, - 0x65U, 0x3AU, 0x20U, 0x4FU, 0x6EU, 0x65U, 0x20U, 0x6FU, 0x72U, 0x20U, 0x6DU, - 0x6FU, 0x72U, 0x65U, 0x20U, 0x69U, 0x6EU, 0x69U, 0x74U, 0x69U, 0x61U, 0x6CU, - 0x20U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, 0x61U, 0x63U, 0x63U, - 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x49U, 0x44U, 0x27U, 0x73U, 0x20U, 0x69U, - 0x73U, 0x20U, 0x6DU, 0x69U, 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x00U, 0x70U, - 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, 0x75U, 0x73U, 0x5FU, 0x74U, 0x6FU, 0x70U, - 0x69U, 0x63U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, 0x00U, 0x56U, 0x6FU, 0x74U, - 0x65U, 0x20U, 0x65U, 0x6EU, 0x74U, 0x72U, 0x79U, 0x20U, 0x64U, 0x65U, 0x6CU, - 0x65U, 0x74U, 0x65U, 0x64U, 0x00U, 0x44U, 0x65U, 0x63U, 0x72U, 0x65U, 0x6DU, - 0x65U, 0x6EU, 0x74U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x63U, 0x6FU, - 0x75U, 0x6EU, 0x74U, 0x20U, 0x64U, 0x65U, 0x6CU, 0x65U, 0x74U, 0x65U, 0x64U, - 0x00U, 0x69U, 0x6DU, 0x63U, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x00U, - 0x70U, 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, 0x75U, 0x73U, 0x5FU, 0x74U, 0x6FU, - 0x70U, 0x69U, 0x63U, 0x5FU, 0x64U, 0x61U, 0x74U, 0x61U, 0x00U, 0x5AU, 0x00U, - 0x56U, 0x00U, 0x54U, 0x00U, 0x49U, 0x52U, 0x52U, 0x00U, 0x44U, 0x42U, 0x47U, - 0x4CU, 0x4EU, 0x00U, 0x4DU, 0x00U, 0x4CU, 0x00U, 0x45U, 0x00U, 0x49U, 0x52U, - 0x44U, 0x00U, 0x49U, 0x4DU, 0x43U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, - 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4DU, 0x69U, 0x73U, 0x73U, - 0x69U, 0x6EU, 0x67U, 0x20U, 0x4CU, 0x20U, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, - 0x65U, 0x74U, 0x65U, 0x72U, 0x2EU, 0x20U, 0x57U, 0x68U, 0x69U, 0x63U, 0x68U, - 0x20U, 0x6CU, 0x61U, 0x79U, 0x65U, 0x72U, 0x20U, 0x61U, 0x72U, 0x65U, 0x20U, - 0x79U, 0x6FU, 0x75U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x69U, 0x6EU, 0x67U, 0x20U, - 0x66U, 0x6FU, 0x72U, 0x3FU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, - 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4CU, 0x32U, 0x73U, 0x20U, 0x63U, - 0x61U, 0x6EU, 0x6EU, 0x6FU, 0x74U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x20U, - 0x6FU, 0x6EU, 0x20U, 0x52U, 0x52U, 0x2FU, 0x52U, 0x44U, 0x20U, 0x61U, 0x74U, - 0x20U, 0x4CU, 0x32U, 0x2CU, 0x20U, 0x64U, 0x69U, 0x64U, 0x20U, 0x79U, 0x6FU, - 0x75U, 0x20U, 0x6DU, 0x65U, 0x61U, 0x6EU, 0x20U, 0x74U, 0x6FU, 0x20U, 0x73U, - 0x65U, 0x74U, 0x20U, 0x4CU, 0x3DU, 0x31U, 0x3FU, 0x00U, 0x74U, 0x6FU, 0x70U, - 0x69U, 0x63U, 0x5FU, 0x64U, 0x61U, 0x74U, 0x61U, 0x5FU, 0x72U, 0x61U, 0x77U, - 0x3AU, 0x00U, 0x4DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x3AU, 0x00U, 0x74U, - 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x70U, 0x61U, 0x64U, 0x64U, 0x69U, 0x6EU, - 0x67U, 0x3AU, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x73U, 0x69U, - 0x7AU, 0x65U, 0x3AU, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x64U, - 0x61U, 0x74U, 0x61U, 0x3AU, 0x00U, 0x50U, 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, - 0x75U, 0x73U, 0x20U, 0x70U, 0x72U, 0x65U, 0x73U, 0x65U, 0x6EU, 0x74U, 0x3DU, - 0x3DU, 0x3AU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, - 0x63U, 0x65U, 0x3AU, 0x20U, 0x45U, 0x6DU, 0x69U, 0x74U, 0x74U, 0x69U, 0x6EU, - 0x67U, 0x20U, 0x69U, 0x6EU, 0x76U, 0x6FU, 0x6BU, 0x65U, 0x20U, 0x74U, 0x6FU, - 0x20U, 0x4CU, 0x31U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, - 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x53U, 0x65U, 0x74U, 0x75U, 0x70U, 0x20U, - 0x63U, 0x6FU, 0x6DU, 0x70U, 0x6CU, 0x65U, 0x74U, 0x65U, 0x64U, 0x20U, 0x73U, - 0x75U, 0x63U, 0x63U, 0x65U, 0x73U, 0x73U, 0x66U, 0x75U, 0x6CU, 0x6CU, 0x79U, - 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, - 0x65U, 0x3AU, 0x20U, 0x4DU, 0x6FU, 0x76U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x65U, - 0x78U, 0x69U, 0x73U, 0x74U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x6DU, 0x65U, 0x6DU, - 0x62U, 0x65U, 0x72U, 0x20U, 0x74U, 0x6FU, 0x20U, 0x6EU, 0x65U, 0x77U, 0x20U, - 0x73U, 0x65U, 0x61U, 0x74U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, + 0x09U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, 0x11U, + 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, 0x19U, 0x20U, + 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x21U, 0x20U, 0x01U, + 0x41U, 0x29U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, 0x3BU, 0x00U, + 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x0BU, 0x05U, 0x20U, 0x01U, 0x41U, + 0x02U, 0x6AU, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0x01U, 0x3AU, 0x00U, 0x00U, + 0x20U, 0x00U, 0x28U, 0x02U, 0x44U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xEEU, + 0x01U, 0x3AU, 0x00U, 0x01U, 0x20U, 0x02U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, + 0x01U, 0x3AU, 0x00U, 0x06U, 0x20U, 0x01U, 0x41U, 0x22U, 0x36U, 0x00U, 0x02U, + 0x20U, 0x02U, 0x41U, 0x7FU, 0x47U, 0x04U, 0x7FU, 0x20U, 0x01U, 0x41U, 0xD0U, + 0x3EU, 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x00U, + 0x37U, 0x03U, 0x09U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x08U, 0x37U, + 0x03U, 0x11U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x10U, 0x37U, 0x03U, + 0x19U, 0x20U, 0x01U, 0x20U, 0x02U, 0x29U, 0x03U, 0x18U, 0x37U, 0x03U, 0x21U, + 0x20U, 0x01U, 0x41U, 0x29U, 0x6AU, 0x05U, 0x20U, 0x01U, 0x41U, 0xFBU, 0x00U, + 0x3BU, 0x00U, 0x07U, 0x20U, 0x01U, 0x41U, 0x09U, 0x6AU, 0x0BU, 0x05U, 0x20U, + 0x01U, 0x41U, 0x02U, 0x6AU, 0x0BU, 0x22U, 0x01U, 0x41U, 0xE1U, 0xE3U, 0x03U, + 0x3BU, 0x00U, 0x00U, 0x20U, 0x01U, 0x20U, 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, + 0x22U, 0x01U, 0x6BU, 0x41U, 0x02U, 0x6AU, 0x21U, 0x02U, 0x20U, 0x00U, 0x20U, + 0x01U, 0x20U, 0x02U, 0x10U, 0x0FU, 0x22U, 0x0BU, 0x3CU, 0x00U, 0xA1U, 0x01U, + 0x20U, 0x00U, 0x41U, 0xE8U, 0x00U, 0x3AU, 0x00U, 0x99U, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xA0U, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x10U, 0x88U, 0x3CU, 0x00U, 0x9FU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0x9EU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x20U, 0x88U, 0x3CU, 0x00U, 0x9DU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x28U, 0x88U, 0x3CU, 0x00U, 0x9CU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x30U, 0x88U, 0x3CU, 0x00U, 0x9BU, 0x01U, 0x20U, 0x00U, + 0x20U, 0x0BU, 0x42U, 0x38U, 0x88U, 0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, + 0x00U, 0x72U, 0x3AU, 0x00U, 0x9AU, 0x01U, 0x41U, 0xC5U, 0x17U, 0x41U, 0x0BU, + 0x20U, 0x01U, 0x20U, 0x02U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0xD0U, + 0x17U, 0x41U, 0x0BU, 0x20U, 0x00U, 0x41U, 0x20U, 0x20U, 0x01U, 0x20U, 0x02U, + 0x10U, 0x10U, 0x22U, 0x0BU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x0BU, 0x42U, 0x20U, + 0x52U, 0x04U, 0x40U, 0x41U, 0xDCU, 0x17U, 0x41U, 0x2FU, 0x42U, 0xA7U, 0x04U, + 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x8BU, 0x18U, 0x41U, 0x0FU, 0x20U, 0x00U, + 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x41U, 0x9AU, 0x18U, 0x41U, + 0x1BU, 0x42U, 0xAAU, 0x04U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, + 0x80U, 0x01U, 0x6AU, 0x41U, 0x0CU, 0x72U, 0x22U, 0x08U, 0x41U, 0x14U, 0x20U, + 0x00U, 0x41U, 0xEDU, 0x09U, 0x6AU, 0x41U, 0x01U, 0x10U, 0x09U, 0x22U, 0x0BU, + 0x42U, 0x14U, 0x51U, 0x04U, 0x40U, 0x41U, 0xB5U, 0x18U, 0x41U, 0x14U, 0x20U, + 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, + 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0xB0U, 0x09U, 0x6AU, 0x41U, 0x0CU, 0x72U, + 0x21U, 0x04U, 0x20U, 0x00U, 0x29U, 0x03U, 0x8CU, 0x01U, 0x20U, 0x00U, 0x29U, + 0x03U, 0xBCU, 0x09U, 0x51U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x28U, + 0x02U, 0x9CU, 0x01U, 0x20U, 0x00U, 0x28U, 0x02U, 0xCCU, 0x09U, 0x47U, 0x20U, + 0x00U, 0x29U, 0x03U, 0x94U, 0x01U, 0x20U, 0x00U, 0x29U, 0x03U, 0xC4U, 0x09U, + 0x52U, 0x72U, 0x0DU, 0x00U, 0x41U, 0xC9U, 0x18U, 0x41U, 0xCDU, 0x00U, 0x42U, + 0xB9U, 0x04U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, + 0x20U, 0x04U, 0x41U, 0x14U, 0x10U, 0x09U, 0x22U, 0x0DU, 0x42U, 0x3FU, 0x88U, + 0xA7U, 0x41U, 0x01U, 0x73U, 0x21U, 0x01U, 0x20U, 0x0DU, 0x42U, 0x00U, 0x59U, + 0x04U, 0x40U, 0x41U, 0x96U, 0x19U, 0x41U, 0x30U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x41U, 0x00U, 0x10U, 0x08U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x20U, 0x0BU, 0x42U, + 0x14U, 0x52U, 0x22U, 0x03U, 0x41U, 0x02U, 0x74U, 0x20U, 0x07U, 0x41U, 0x01U, + 0x74U, 0x72U, 0x72U, 0x22U, 0x02U, 0xADU, 0x21U, 0x0EU, 0x20U, 0x02U, 0x41U, + 0x03U, 0x6BU, 0x22U, 0x05U, 0x41U, 0x04U, 0x4DU, 0x04U, 0x40U, 0x02U, 0x40U, + 0x02U, 0x40U, 0x20U, 0x05U, 0x41U, 0x01U, 0x6BU, 0x0EU, 0x03U, 0x01U, 0x01U, + 0x01U, 0x00U, 0x0BU, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xC5U, 0x04U, + 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0xC6U, 0x19U, 0x41U, 0x02U, 0x20U, + 0x0EU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xC9U, 0x19U, 0x41U, 0x02U, 0x20U, 0x03U, + 0xADU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xCBU, 0x19U, 0x41U, 0x02U, 0x20U, 0x0FU, + 0x10U, 0x03U, 0x1AU, 0x41U, 0xCDU, 0x19U, 0x41U, 0x02U, 0x20U, 0x01U, 0xADU, + 0x10U, 0x03U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x02U, 0x41U, 0x01U, 0x6BU, 0x22U, + 0x01U, 0x41U, 0x03U, 0x4DU, 0x04U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U, + 0x01U, 0x41U, 0x02U, 0x6BU, 0x0EU, 0x02U, 0x03U, 0x00U, 0x01U, 0x0BU, 0x20U, + 0x0CU, 0x42U, 0x01U, 0x7CU, 0x21U, 0x0CU, 0x0CU, 0x02U, 0x0BU, 0x20U, 0x0CU, + 0x42U, 0x01U, 0x7DU, 0x21U, 0x0CU, 0x0BU, 0x0BU, 0x41U, 0xCFU, 0x19U, 0x41U, + 0x10U, 0x20U, 0x0BU, 0x42U, 0x14U, 0x51U, 0xADU, 0x10U, 0x03U, 0x1AU, 0x41U, + 0xB5U, 0x13U, 0x41U, 0x0FU, 0x20U, 0x0FU, 0x10U, 0x03U, 0x1AU, 0x41U, 0xCEU, + 0x0AU, 0x41U, 0x0CU, 0x20U, 0x0CU, 0x10U, 0x03U, 0x1AU, 0x20U, 0x0CU, 0x42U, + 0x01U, 0x57U, 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xE7U, + 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x20U, 0x0CU, 0x3CU, 0x00U, + 0x20U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x01U, 0x41U, 0xEFU, 0x09U, + 0x41U, 0x02U, 0x10U, 0x0CU, 0x42U, 0x01U, 0x52U, 0x04U, 0x40U, 0x41U, 0xB4U, + 0x0AU, 0x41U, 0x1AU, 0x42U, 0xEAU, 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, + 0x0DU, 0x42U, 0x00U, 0x59U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x20U, + 0x0DU, 0x3CU, 0x00U, 0x20U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U, + 0x20U, 0x6AU, 0x41U, 0x01U, 0x10U, 0x0CU, 0x50U, 0x45U, 0x04U, 0x40U, 0x41U, + 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xF4U, 0x04U, 0x10U, 0x0BU, 0x1AU, 0x0BU, + 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x41U, 0x14U, 0x10U, 0x0CU, 0x50U, + 0x0DU, 0x00U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xF7U, 0x04U, 0x10U, + 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x20U, 0x0BU, 0x42U, 0x14U, 0x51U, 0x04U, 0x40U, + 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0xD6U, 0x00U, 0x3AU, 0x00U, 0x80U, 0x01U, + 0x20U, 0x00U, 0x41U, 0x38U, 0x6AU, 0x21U, 0x06U, 0x20U, 0x00U, 0x41U, 0x30U, + 0x6AU, 0x21U, 0x09U, 0x41U, 0x01U, 0x21U, 0x03U, 0x03U, 0x40U, 0x41U, 0x80U, + 0x85U, 0x80U, 0x80U, 0x78U, 0x41U, 0x03U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x03U, + 0x41U, 0x03U, 0x46U, 0x45U, 0x04U, 0x40U, 0x41U, 0x00U, 0x21U, 0x01U, 0x03U, + 0x40U, 0x41U, 0x82U, 0x85U, 0x80U, 0x80U, 0x78U, 0x41U, 0xC3U, 0x00U, 0x10U, + 0x00U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x46U, 0x0DU, 0x00U, + 0x41U, 0xD2U, 0x00U, 0x21U, 0x02U, 0x20U, 0x00U, 0x41U, 0xD2U, 0x00U, 0x41U, + 0xC8U, 0x00U, 0x41U, 0xD3U, 0x00U, 0x20U, 0x01U, 0x41U, 0x0CU, 0x49U, 0x22U, + 0x0AU, 0x1BU, 0x20U, 0x01U, 0x41U, 0x02U, 0x49U, 0x1BU, 0x22U, 0x05U, 0x3AU, + 0x00U, 0x81U, 0x01U, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x01U, 0x4BU, 0x04U, + 0x7FU, 0x41U, 0x7EU, 0x41U, 0x74U, 0x20U, 0x0AU, 0x1BU, 0x20U, 0x01U, 0x6AU, + 0x05U, 0x20U, 0x01U, 0x41U, 0x01U, 0x47U, 0x0DU, 0x01U, 0x41U, 0xC4U, 0x00U, + 0x0BU, 0x21U, 0x02U, 0x0BU, 0x20U, 0x00U, 0x20U, 0x03U, 0x3AU, 0x00U, 0x83U, + 0x01U, 0x20U, 0x00U, 0x20U, 0x02U, 0x3AU, 0x00U, 0x82U, 0x01U, 0x20U, 0x06U, + 0x42U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x09U, 0x42U, 0x00U, 0x37U, 0x03U, + 0x00U, 0x20U, 0x00U, 0x42U, 0x00U, 0x37U, 0x03U, 0x28U, 0x20U, 0x00U, 0x42U, + 0x00U, 0x37U, 0x03U, 0x20U, 0x20U, 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x20U, + 0x41U, 0x20U, 0x41U, 0x14U, 0x41U, 0x08U, 0x20U, 0x05U, 0x41U, 0xD3U, 0x00U, + 0x46U, 0x1BU, 0x20U, 0x05U, 0x41U, 0xC8U, 0x00U, 0x46U, 0x1BU, 0x22U, 0x02U, + 0x6BU, 0x41U, 0xFCU, 0x01U, 0x71U, 0x6AU, 0x20U, 0x02U, 0x20U, 0x00U, 0x41U, + 0x80U, 0x01U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x09U, 0x20U, 0x02U, 0xADU, 0x51U, + 0x04U, 0x40U, 0x20U, 0x00U, 0x41U, 0xC3U, 0x00U, 0x3AU, 0x00U, 0x20U, 0x20U, + 0x00U, 0x41U, 0x00U, 0x3AU, 0x00U, 0x50U, 0x20U, 0x00U, 0x20U, 0x03U, 0x3AU, + 0x00U, 0x23U, 0x20U, 0x00U, 0x20U, 0x00U, 0x2FU, 0x00U, 0x81U, 0x01U, 0x3BU, + 0x00U, 0x21U, 0x20U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x41U, 0x01U, 0x20U, + 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x09U, 0x42U, 0x01U, 0x51U, + 0x04U, 0x40U, 0x20U, 0x00U, 0x2DU, 0x00U, 0x50U, 0x22U, 0x02U, 0x41U, 0x01U, + 0x4BU, 0x04U, 0x40U, 0x20U, 0x00U, 0x20U, 0x02U, 0x41U, 0x01U, 0x6BU, 0x3AU, + 0x00U, 0x50U, 0x20U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x41U, 0x01U, 0x20U, + 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, 0x42U, 0x01U, 0x52U, + 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xA8U, 0x05U, 0x10U, + 0x0BU, 0x1AU, 0x0BU, 0x41U, 0xFDU, 0x19U, 0x41U, 0x18U, 0x20U, 0x00U, 0x31U, + 0x00U, 0x50U, 0x10U, 0x03U, 0x1AU, 0x05U, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, + 0x00U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, 0x50U, 0x45U, 0x04U, + 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xA2U, 0x05U, 0x10U, 0x0BU, + 0x1AU, 0x0BU, 0x41U, 0xE0U, 0x19U, 0x41U, 0x1DU, 0x20U, 0x00U, 0x31U, 0x00U, + 0x50U, 0x10U, 0x03U, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, + 0x00U, 0x41U, 0x80U, 0x01U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x0CU, 0x50U, 0x45U, + 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xAEU, 0x05U, 0x10U, + 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x95U, 0x1AU, 0x41U, 0x13U, 0x20U, 0x00U, 0x41U, + 0x20U, 0x6AU, 0x41U, 0x20U, 0x41U, 0x01U, 0x10U, 0x08U, 0x1AU, 0x0BU, 0x20U, + 0x01U, 0x41U, 0x01U, 0x6AU, 0x21U, 0x01U, 0x0CU, 0x01U, 0x0BU, 0x0BU, 0x20U, + 0x03U, 0x41U, 0x01U, 0x6AU, 0x21U, 0x03U, 0x0CU, 0x01U, 0x0BU, 0x0BU, 0x41U, + 0x00U, 0x41U, 0x00U, 0x20U, 0x00U, 0x41U, 0xEDU, 0x09U, 0x6AU, 0x41U, 0x01U, + 0x10U, 0x0CU, 0x50U, 0x45U, 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, + 0x42U, 0xB6U, 0x05U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, + 0x20U, 0x08U, 0x41U, 0x14U, 0x10U, 0x0CU, 0x50U, 0x0DU, 0x00U, 0x41U, 0xB4U, + 0x0AU, 0x41U, 0x1AU, 0x42U, 0xB9U, 0x05U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x0BU, + 0x20U, 0x07U, 0x45U, 0x04U, 0x40U, 0x02U, 0x40U, 0x20U, 0x04U, 0x41U, 0x14U, + 0x20U, 0x00U, 0x41U, 0xEDU, 0x09U, 0x6AU, 0x41U, 0x01U, 0x10U, 0x0CU, 0x42U, + 0x14U, 0x52U, 0x04U, 0x40U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xC0U, + 0x05U, 0x10U, 0x0BU, 0x1AU, 0x0BU, 0x20U, 0x00U, 0x41U, 0xEDU, 0x09U, 0x6AU, + 0x41U, 0x01U, 0x20U, 0x04U, 0x41U, 0x14U, 0x10U, 0x0CU, 0x42U, 0x01U, 0x51U, + 0x0DU, 0x00U, 0x41U, 0xB4U, 0x0AU, 0x41U, 0x1AU, 0x42U, 0xC3U, 0x05U, 0x10U, + 0x0BU, 0x1AU, 0x0BU, 0x0BU, 0x41U, 0xA8U, 0x1AU, 0x41U, 0x22U, 0x42U, 0xC6U, + 0x05U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xCAU, 0x1AU, 0x41U, 0x22U, 0x42U, + 0xCAU, 0x05U, 0x10U, 0x0BU, 0x1AU, 0x20U, 0x00U, 0x41U, 0xB0U, 0x0AU, 0x6AU, + 0x24U, 0x00U, 0x20U, 0x0CU, 0x0BU, 0x0BU, 0xF9U, 0x12U, 0x02U, 0x00U, 0x41U, + 0x80U, 0x08U, 0x0BU, 0x14U, 0xB5U, 0xF7U, 0x62U, 0x79U, 0x8AU, 0x53U, 0xD5U, + 0x43U, 0xA0U, 0x14U, 0xCAU, 0xF8U, 0xB2U, 0x97U, 0xCFU, 0xF8U, 0xF2U, 0xF9U, + 0x37U, 0xE8U, 0x00U, 0x41U, 0x94U, 0x08U, 0x0BU, 0xD7U, 0x12U, 0x44U, 0x00U, + 0x44U, 0x42U, 0x47U, 0x4CU, 0x4EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x50U, 0x61U, 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x6EU, 0x6FU, 0x6EU, 0x2DU, 0x49U, 0x6EU, 0x76U, 0x6FU, 0x6BU, 0x65U, 0x20U, 0x74U, 0x78U, 0x6EU, 0x2EU, 0x20U, 0x48U, 0x6FU, 0x6FU, 0x6BU, 0x4FU, 0x6EU, 0x20U, 0x73U, 0x68U, 0x6FU, 0x75U, 0x6CU, 0x64U, 0x20U, 0x62U, 0x65U, 0x20U, 0x63U, 0x68U, 0x61U, 0x6EU, 0x67U, 0x65U, 0x64U, 0x20U, 0x74U, 0x6FU, 0x20U, 0x61U, 0x76U, 0x6FU, 0x69U, 0x64U, 0x20U, 0x74U, - 0x68U, 0x69U, 0x73U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, - 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x74U, 0x65U, 0x72U, - 0x6EU, 0x61U, 0x6CU, 0x20U, 0x6CU, 0x6FU, 0x67U, 0x69U, 0x63U, 0x20U, 0x65U, - 0x72U, 0x72U, 0x6FU, 0x72U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, - 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x56U, 0x61U, 0x6CU, 0x69U, - 0x64U, 0x20U, 0x54U, 0x4FU, 0x50U, 0x49U, 0x43U, 0x20U, 0x6DU, 0x75U, 0x73U, - 0x74U, 0x20U, 0x62U, 0x65U, 0x20U, 0x73U, 0x70U, 0x65U, 0x63U, 0x69U, 0x66U, - 0x69U, 0x65U, 0x64U, 0x20U, 0x61U, 0x73U, 0x20U, 0x6FU, 0x74U, 0x78U, 0x6EU, - 0x20U, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, 0x74U, 0x65U, 0x72U, 0x2EU, + 0x68U, 0x69U, 0x73U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x61U, + 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x50U, 0x61U, 0x73U, 0x73U, 0x69U, 0x6EU, + 0x67U, 0x20U, 0x6FU, 0x75U, 0x74U, 0x67U, 0x6FU, 0x69U, 0x6EU, 0x67U, 0x20U, + 0x74U, 0x78U, 0x6EU, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, + 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x53U, 0x74U, 0x61U, 0x72U, 0x74U, + 0x69U, 0x6EU, 0x67U, 0x20U, 0x67U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, + 0x6EU, 0x63U, 0x65U, 0x20U, 0x6CU, 0x6FU, 0x67U, 0x69U, 0x63U, 0x20U, 0x6FU, + 0x6EU, 0x20U, 0x4CU, 0x31U, 0x20U, 0x74U, 0x61U, 0x62U, 0x6CU, 0x65U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, - 0x3AU, 0x20U, 0x41U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, 0x6EU, 0x67U, - 0x20U, 0x73U, 0x65U, 0x61U, 0x74U, 0x20U, 0x63U, 0x68U, 0x61U, 0x6EU, 0x67U, - 0x65U, 0x2CU, 0x20U, 0x62U, 0x75U, 0x74U, 0x20U, 0x73U, 0x65U, 0x61U, 0x74U, - 0x20U, 0x61U, 0x6CU, 0x72U, 0x65U, 0x61U, 0x64U, 0x79U, 0x20U, 0x63U, 0x6FU, - 0x6EU, 0x74U, 0x61U, 0x69U, 0x6EU, 0x73U, 0x20U, 0x74U, 0x68U, 0x65U, 0x20U, - 0x6EU, 0x65U, 0x77U, 0x20U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x2EU, - 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, - 0x20U, 0x50U, 0x61U, 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x6FU, 0x75U, - 0x74U, 0x67U, 0x6FU, 0x69U, 0x6EU, 0x67U, 0x20U, 0x74U, 0x78U, 0x6EU, 0x2EU, - 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, - 0x3AU, 0x20U, 0x56U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x72U, 0x65U, 0x63U, 0x6FU, - 0x72U, 0x64U, 0x2EU, 0x20U, 0x4EU, 0x6FU, 0x74U, 0x20U, 0x79U, 0x65U, 0x74U, - 0x20U, 0x65U, 0x6EU, 0x6FU, 0x75U, 0x67U, 0x68U, 0x20U, 0x76U, 0x6FU, 0x74U, - 0x65U, 0x73U, 0x20U, 0x74U, 0x6FU, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, - 0x6EU, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x61U, 0x6EU, 0x63U, - 0x65U, 0x3AU, 0x20U, 0x48U, 0x6FU, 0x6FU, 0x6BU, 0x20U, 0x48U, 0x61U, 0x73U, - 0x68U, 0x20U, 0x64U, 0x6FU, 0x65U, 0x73U, 0x6EU, 0x27U, 0x74U, 0x20U, 0x65U, - 0x78U, 0x69U, 0x73U, 0x74U, 0x20U, 0x6FU, 0x6EU, 0x20U, 0x6CU, 0x65U, 0x64U, - 0x67U, 0x65U, 0x72U, 0x20U, 0x77U, 0x68U, 0x69U, 0x6CU, 0x65U, 0x20U, 0x61U, - 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, 0x6EU, 0x67U, 0x20U, 0x68U, 0x6FU, - 0x6FU, 0x6BU, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x61U, 0x6EU, - 0x63U, 0x65U, 0x3AU, 0x20U, 0x54U, 0x61U, 0x72U, 0x67U, 0x65U, 0x74U, 0x20U, - 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x20U, 0x69U, 0x73U, 0x20U, 0x61U, 0x6CU, 0x72U, - 0x65U, 0x61U, 0x64U, 0x79U, 0x20U, 0x74U, 0x68U, 0x65U, 0x20U, 0x73U, 0x61U, - 0x6DU, 0x65U, 0x20U, 0x61U, 0x73U, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, - 0x6EU, 0x65U, 0x64U, 0x20U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x2EU, 0x00U, 0x47U, - 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, - 0x45U, 0x6DU, 0x69U, 0x74U, 0x20U, 0x66U, 0x61U, 0x69U, 0x6CU, 0x65U, 0x64U, - 0x20U, 0x64U, 0x75U, 0x72U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x68U, 0x6FU, 0x6FU, - 0x6BU, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, 0x6EU, 0x67U, - 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, - 0x65U, 0x3AU, 0x20U, 0x53U, 0x75U, 0x63U, 0x63U, 0x65U, 0x73U, 0x73U, 0x66U, - 0x75U, 0x6CU, 0x6CU, 0x79U, 0x20U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x74U, 0x65U, - 0x64U, 0x20U, 0x4CU, 0x31U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x2EU, 0x00U, - 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, - 0x20U, 0x4DU, 0x69U, 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x6FU, 0x72U, - 0x20U, 0x69U, 0x6EU, 0x63U, 0x6FU, 0x72U, 0x72U, 0x65U, 0x63U, 0x74U, 0x20U, - 0x73U, 0x69U, 0x7AU, 0x65U, 0x20U, 0x6FU, 0x66U, 0x20U, 0x56U, 0x4FU, 0x54U, - 0x45U, 0x20U, 0x64U, 0x61U, 0x74U, 0x61U, 0x20U, 0x66U, 0x6FU, 0x72U, 0x20U, - 0x54U, 0x4FU, 0x50U, 0x49U, 0x43U, 0x20U, 0x74U, 0x79U, 0x70U, 0x65U, 0x2EU, - 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, - 0x3AU, 0x20U, 0x59U, 0x6FU, 0x75U, 0x20U, 0x61U, 0x72U, 0x65U, 0x20U, 0x6EU, - 0x6FU, 0x74U, 0x20U, 0x63U, 0x75U, 0x72U, 0x72U, 0x65U, 0x6EU, 0x74U, 0x6CU, - 0x79U, 0x20U, 0x61U, 0x20U, 0x67U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, - 0x6EU, 0x63U, 0x65U, 0x20U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, - 0x61U, 0x74U, 0x20U, 0x74U, 0x68U, 0x69U, 0x73U, 0x20U, 0x74U, 0x61U, 0x62U, - 0x6CU, 0x65U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, - 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x53U, 0x74U, 0x61U, 0x72U, 0x74U, 0x69U, - 0x6EU, 0x67U, 0x20U, 0x67U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, - 0x63U, 0x65U, 0x20U, 0x6CU, 0x6FU, 0x67U, 0x69U, 0x63U, 0x20U, 0x6FU, 0x6EU, - 0x20U, 0x4CU, 0x32U, 0x20U, 0x74U, 0x61U, 0x62U, 0x6CU, 0x65U, 0x2EU, 0x00U, - 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, - 0x20U, 0x53U, 0x74U, 0x61U, 0x72U, 0x74U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x67U, - 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x20U, 0x6CU, - 0x6FU, 0x67U, 0x69U, 0x63U, 0x20U, 0x6FU, 0x6EU, 0x20U, 0x4CU, 0x31U, 0x20U, - 0x74U, 0x61U, 0x62U, 0x6CU, 0x65U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, - 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x41U, 0x63U, 0x74U, - 0x69U, 0x6FU, 0x6EU, 0x20U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, - 0x63U, 0x68U, 0x61U, 0x6EU, 0x67U, 0x65U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, - 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x48U, 0x6FU, - 0x6FU, 0x6BU, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x65U, 0x64U, - 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x3AU, 0x20U, 0x41U, - 0x73U, 0x73U, 0x65U, 0x72U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x20U, 0x66U, 0x61U, - 0x69U, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, - 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4CU, 0x31U, 0x20U, 0x76U, - 0x6FU, 0x74U, 0x65U, 0x20U, 0x65U, 0x6DU, 0x69U, 0x73U, 0x73U, 0x69U, 0x6FU, - 0x6EU, 0x20U, 0x66U, 0x61U, 0x69U, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x00U, 0x47U, - 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, - 0x59U, 0x6FU, 0x75U, 0x72U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x69U, - 0x73U, 0x20U, 0x61U, 0x6CU, 0x72U, 0x65U, 0x61U, 0x64U, 0x79U, 0x20U, 0x63U, - 0x61U, 0x73U, 0x74U, 0x20U, 0x74U, 0x68U, 0x69U, 0x73U, 0x20U, 0x77U, 0x61U, - 0x79U, 0x20U, 0x66U, 0x6FU, 0x72U, 0x20U, 0x74U, 0x68U, 0x69U, 0x73U, 0x20U, - 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, - 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x56U, 0x61U, 0x6CU, - 0x69U, 0x64U, 0x20U, 0x73U, 0x65U, 0x61U, 0x74U, 0x20U, 0x74U, 0x6FU, 0x70U, - 0x69U, 0x63U, 0x73U, 0x20U, 0x61U, 0x72U, 0x65U, 0x20U, 0x30U, 0x20U, 0x74U, - 0x68U, 0x72U, 0x6FU, 0x75U, 0x67U, 0x68U, 0x20U, 0x31U, 0x39U, 0x2EU, 0x00U, - 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, - 0x20U, 0x56U, 0x61U, 0x6CU, 0x69U, 0x64U, 0x20U, 0x68U, 0x6FU, 0x6FU, 0x6BU, - 0x20U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x73U, 0x20U, 0x61U, 0x72U, 0x65U, - 0x20U, 0x30U, 0x20U, 0x74U, 0x68U, 0x72U, 0x6FU, 0x75U, 0x67U, 0x68U, 0x20U, - 0x39U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, - 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, 0x69U, 0x61U, 0x6CU, - 0x20U, 0x52U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, 0x20U, 0x44U, 0x65U, 0x6CU, - 0x61U, 0x79U, 0x20U, 0x6DU, 0x75U, 0x73U, 0x74U, 0x20U, 0x62U, 0x65U, 0x20U, - 0x3EU, 0x20U, 0x30U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, - 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, 0x69U, - 0x61U, 0x6CU, 0x20U, 0x4DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, 0x43U, - 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x6DU, 0x75U, 0x73U, 0x74U, 0x20U, 0x62U, - 0x65U, 0x20U, 0x3EU, 0x20U, 0x30U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, - 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4EU, 0x6FU, 0x74U, - 0x20U, 0x79U, 0x65U, 0x74U, 0x20U, 0x65U, 0x6EU, 0x6FU, 0x75U, 0x67U, 0x68U, - 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x20U, 0x74U, 0x6FU, 0x20U, 0x61U, - 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x20U, 0x4CU, 0x31U, 0x20U, 0x76U, 0x6FU, - 0x74U, 0x65U, 0x2EU, 0x2EU, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, - 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x56U, 0x61U, 0x6CU, 0x69U, - 0x64U, 0x20U, 0x72U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, 0x20U, 0x74U, 0x6FU, - 0x70U, 0x69U, 0x63U, 0x73U, 0x20U, 0x61U, 0x72U, 0x65U, 0x20U, 0x52U, 0x20U, - 0x28U, 0x72U, 0x61U, 0x74U, 0x65U, 0x29U, 0x20U, 0x61U, 0x6EU, 0x64U, 0x20U, - 0x44U, 0x20U, 0x28U, 0x64U, 0x65U, 0x6CU, 0x61U, 0x79U, 0x29U, 0x2EU, 0x00U, - 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, - 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, 0x69U, 0x61U, 0x6CU, 0x20U, 0x52U, 0x65U, - 0x77U, 0x61U, 0x72U, 0x64U, 0x20U, 0x52U, 0x61U, 0x74U, 0x65U, 0x20U, 0x50U, - 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, 0x74U, 0x65U, 0x72U, 0x20U, 0x6DU, 0x69U, - 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x28U, 0x49U, 0x52U, 0x52U, 0x29U, - 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, + 0x3AU, 0x20U, 0x53U, 0x74U, 0x61U, 0x72U, 0x74U, 0x69U, 0x6EU, 0x67U, 0x20U, + 0x67U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x20U, + 0x6CU, 0x6FU, 0x67U, 0x69U, 0x63U, 0x20U, 0x6FU, 0x6EU, 0x20U, 0x4CU, 0x32U, + 0x20U, 0x74U, 0x61U, 0x62U, 0x6CU, 0x65U, 0x2EU, 0x00U, 0x4DU, 0x43U, 0x00U, + 0x49U, 0x4DU, 0x43U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, + 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, 0x69U, 0x61U, + 0x6CU, 0x20U, 0x4DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, 0x43U, 0x6FU, + 0x75U, 0x6EU, 0x74U, 0x20U, 0x50U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, 0x74U, + 0x65U, 0x72U, 0x20U, 0x6DU, 0x69U, 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x20U, + 0x28U, 0x49U, 0x4DU, 0x43U, 0x29U, 0x2EU, 0x00U, 0x69U, 0x6DU, 0x63U, 0x00U, + 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x3AU, 0x20U, 0x41U, 0x73U, 0x73U, + 0x65U, 0x72U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x20U, 0x66U, 0x61U, 0x69U, 0x6CU, + 0x65U, 0x64U, 0x2EU, 0x00U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x5FU, + 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, + 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, + 0x69U, 0x61U, 0x6CU, 0x20U, 0x4DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, + 0x43U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x6DU, 0x75U, 0x73U, 0x74U, 0x20U, + 0x62U, 0x65U, 0x20U, 0x3EU, 0x20U, 0x30U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, + 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, + 0x69U, 0x74U, 0x69U, 0x61U, 0x6CU, 0x20U, 0x4DU, 0x65U, 0x6DU, 0x62U, 0x65U, + 0x72U, 0x20U, 0x43U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x6DU, 0x75U, 0x73U, + 0x74U, 0x20U, 0x62U, 0x65U, 0x20U, 0x3CU, 0x3DU, 0x20U, 0x53U, 0x65U, 0x61U, + 0x74U, 0x20U, 0x43U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x28U, 0x32U, 0x30U, + 0x29U, 0x2EU, 0x00U, 0x49U, 0x52U, 0x52U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, + 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, + 0x74U, 0x69U, 0x61U, 0x6CU, 0x20U, 0x52U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, + 0x20U, 0x52U, 0x61U, 0x74U, 0x65U, 0x20U, 0x50U, 0x61U, 0x72U, 0x61U, 0x6DU, + 0x65U, 0x74U, 0x65U, 0x72U, 0x20U, 0x6DU, 0x69U, 0x73U, 0x73U, 0x69U, 0x6EU, + 0x67U, 0x20U, 0x28U, 0x49U, 0x52U, 0x52U, 0x29U, 0x2EU, 0x00U, 0x49U, 0x52U, + 0x44U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, 0x69U, 0x61U, 0x6CU, 0x20U, 0x52U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, 0x20U, 0x44U, 0x65U, 0x6CU, 0x61U, 0x79U, 0x20U, 0x50U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, 0x74U, 0x65U, 0x72U, 0x20U, 0x6DU, 0x69U, 0x73U, 0x73U, 0x20U, 0x28U, 0x49U, 0x52U, 0x44U, 0x29U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, 0x69U, 0x61U, 0x6CU, 0x20U, - 0x4DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, 0x43U, 0x6FU, 0x75U, 0x6EU, - 0x74U, 0x20U, 0x50U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, 0x74U, 0x65U, 0x72U, - 0x20U, 0x6DU, 0x69U, 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x28U, 0x49U, - 0x4DU, 0x43U, 0x29U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, - 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x69U, 0x74U, 0x69U, - 0x61U, 0x6CU, 0x20U, 0x4DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, 0x43U, - 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x6DU, 0x75U, 0x73U, 0x74U, 0x20U, 0x62U, - 0x65U, 0x20U, 0x3CU, 0x3DU, 0x20U, 0x53U, 0x65U, 0x61U, 0x74U, 0x20U, 0x43U, - 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x28U, 0x32U, 0x30U, 0x29U, 0x2EU, 0x00U, - 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, - 0x20U, 0x4CU, 0x61U, 0x79U, 0x65U, 0x72U, 0x20U, 0x70U, 0x61U, 0x72U, 0x61U, - 0x6DU, 0x65U, 0x74U, 0x65U, 0x72U, 0x20U, 0x6DU, 0x75U, 0x73U, 0x74U, 0x20U, - 0x62U, 0x65U, 0x20U, 0x27U, 0x31U, 0x27U, 0x20U, 0x6FU, 0x72U, 0x20U, 0x27U, - 0x32U, 0x27U, 0x2EU, 0x00U, 0x22U, 0x41U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, - 0x69U, 0x6EU, 0x67U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x22U, 0x00U, - 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, - 0x20U, 0x52U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, 0x20U, 0x64U, 0x65U, 0x6CU, - 0x61U, 0x79U, 0x20U, 0x63U, 0x68U, 0x61U, 0x6EU, 0x67U, 0x65U, 0x20U, 0x61U, - 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x65U, 0x64U, 0x21U, 0x00U, 0x47U, 0x6FU, - 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x52U, - 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, 0x20U, 0x72U, 0x61U, 0x74U, 0x65U, 0x20U, - 0x63U, 0x68U, 0x61U, 0x6EU, 0x67U, 0x65U, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, - 0x6FU, 0x6EU, 0x65U, 0x64U, 0x21U, 0x00U, 0x41U, 0xB0U, 0x1AU, 0x0BU, 0x14U, - 0xB5U, 0xF7U, 0x62U, 0x79U, 0x8AU, 0x53U, 0xD5U, 0x43U, 0xA0U, 0x14U, 0xCAU, - 0xF8U, 0xB2U, 0x97U, 0xCFU, 0xF8U, 0xF2U, 0xF9U, 0x37U, 0xE8U}; + 0x52U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, 0x20U, 0x44U, 0x65U, 0x6CU, 0x61U, + 0x79U, 0x20U, 0x6DU, 0x75U, 0x73U, 0x74U, 0x20U, 0x62U, 0x65U, 0x20U, 0x3EU, + 0x20U, 0x30U, 0x2EU, 0x00U, 0x52U, 0x52U, 0x00U, 0x52U, 0x44U, 0x00U, 0x47U, + 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, + 0x4FU, 0x6EU, 0x65U, 0x20U, 0x6FU, 0x72U, 0x20U, 0x6DU, 0x6FU, 0x72U, 0x65U, + 0x20U, 0x69U, 0x6EU, 0x69U, 0x74U, 0x69U, 0x61U, 0x6CU, 0x20U, 0x6DU, 0x65U, + 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, + 0x74U, 0x20U, 0x49U, 0x44U, 0x27U, 0x73U, 0x20U, 0x69U, 0x73U, 0x20U, 0x6DU, + 0x69U, 0x73U, 0x73U, 0x69U, 0x6EU, 0x67U, 0x00U, 0x4DU, 0x65U, 0x6DU, 0x62U, + 0x65U, 0x72U, 0x3AU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, + 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x53U, 0x65U, 0x74U, 0x75U, 0x70U, 0x20U, + 0x63U, 0x6FU, 0x6DU, 0x70U, 0x6CU, 0x65U, 0x74U, 0x65U, 0x64U, 0x20U, 0x73U, + 0x75U, 0x63U, 0x63U, 0x65U, 0x73U, 0x73U, 0x66U, 0x75U, 0x6CU, 0x6CU, 0x79U, + 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, + 0x65U, 0x3AU, 0x20U, 0x59U, 0x6FU, 0x75U, 0x20U, 0x61U, 0x72U, 0x65U, 0x20U, + 0x6EU, 0x6FU, 0x74U, 0x20U, 0x63U, 0x75U, 0x72U, 0x72U, 0x65U, 0x6EU, 0x74U, + 0x6CU, 0x79U, 0x20U, 0x61U, 0x20U, 0x67U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, + 0x61U, 0x6EU, 0x63U, 0x65U, 0x20U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, + 0x20U, 0x61U, 0x74U, 0x20U, 0x74U, 0x68U, 0x69U, 0x73U, 0x20U, 0x74U, 0x61U, + 0x62U, 0x6CU, 0x65U, 0x2EU, 0x00U, 0x54U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, + 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x56U, 0x61U, 0x6CU, + 0x69U, 0x64U, 0x20U, 0x54U, 0x4FU, 0x50U, 0x49U, 0x43U, 0x20U, 0x6DU, 0x75U, + 0x73U, 0x74U, 0x20U, 0x62U, 0x65U, 0x20U, 0x73U, 0x70U, 0x65U, 0x63U, 0x69U, + 0x66U, 0x69U, 0x65U, 0x64U, 0x20U, 0x61U, 0x73U, 0x20U, 0x6FU, 0x74U, 0x78U, + 0x6EU, 0x20U, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, 0x74U, 0x65U, 0x72U, + 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, + 0x65U, 0x3AU, 0x20U, 0x56U, 0x61U, 0x6CU, 0x69U, 0x64U, 0x20U, 0x73U, 0x65U, + 0x61U, 0x74U, 0x20U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x73U, 0x20U, 0x61U, + 0x72U, 0x65U, 0x20U, 0x30U, 0x20U, 0x74U, 0x68U, 0x72U, 0x6FU, 0x75U, 0x67U, + 0x68U, 0x20U, 0x31U, 0x39U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, + 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x56U, 0x61U, 0x6CU, 0x69U, + 0x64U, 0x20U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x20U, 0x74U, 0x6FU, 0x70U, 0x69U, + 0x63U, 0x73U, 0x20U, 0x61U, 0x72U, 0x65U, 0x20U, 0x30U, 0x20U, 0x74U, 0x68U, + 0x72U, 0x6FU, 0x75U, 0x67U, 0x68U, 0x20U, 0x39U, 0x2EU, 0x00U, 0x47U, 0x6FU, + 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x56U, + 0x61U, 0x6CU, 0x69U, 0x64U, 0x20U, 0x72U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, + 0x20U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x73U, 0x20U, 0x61U, 0x72U, 0x65U, + 0x20U, 0x52U, 0x20U, 0x28U, 0x72U, 0x61U, 0x74U, 0x65U, 0x29U, 0x20U, 0x61U, + 0x6EU, 0x64U, 0x20U, 0x44U, 0x20U, 0x28U, 0x64U, 0x65U, 0x6CU, 0x61U, 0x79U, + 0x29U, 0x2EU, 0x00U, 0x4CU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, + 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4DU, 0x69U, 0x73U, 0x73U, 0x69U, + 0x6EU, 0x67U, 0x20U, 0x4CU, 0x20U, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, + 0x74U, 0x65U, 0x72U, 0x2EU, 0x20U, 0x57U, 0x68U, 0x69U, 0x63U, 0x68U, 0x20U, + 0x6CU, 0x61U, 0x79U, 0x65U, 0x72U, 0x20U, 0x61U, 0x72U, 0x65U, 0x20U, 0x79U, + 0x6FU, 0x75U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x66U, + 0x6FU, 0x72U, 0x3FU, 0x00U, 0x6CU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, + 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4CU, 0x61U, 0x79U, 0x65U, + 0x72U, 0x20U, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x65U, 0x74U, 0x65U, 0x72U, + 0x20U, 0x6DU, 0x75U, 0x73U, 0x74U, 0x20U, 0x62U, 0x65U, 0x20U, 0x27U, 0x31U, + 0x27U, 0x20U, 0x6FU, 0x72U, 0x20U, 0x27U, 0x32U, 0x27U, 0x2EU, 0x00U, 0x47U, + 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, + 0x4CU, 0x32U, 0x73U, 0x20U, 0x63U, 0x61U, 0x6EU, 0x6EU, 0x6FU, 0x74U, 0x20U, + 0x76U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x6FU, 0x6EU, 0x20U, 0x52U, 0x52U, 0x2FU, + 0x52U, 0x44U, 0x20U, 0x61U, 0x74U, 0x20U, 0x4CU, 0x32U, 0x2CU, 0x20U, 0x64U, + 0x69U, 0x64U, 0x20U, 0x79U, 0x6FU, 0x75U, 0x20U, 0x6DU, 0x65U, 0x61U, 0x6EU, + 0x20U, 0x74U, 0x6FU, 0x20U, 0x73U, 0x65U, 0x74U, 0x20U, 0x4CU, 0x3DU, 0x31U, + 0x3FU, 0x00U, 0x56U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, + 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4DU, 0x69U, 0x73U, 0x73U, 0x69U, 0x6EU, + 0x67U, 0x20U, 0x6FU, 0x72U, 0x20U, 0x69U, 0x6EU, 0x63U, 0x6FU, 0x72U, 0x72U, + 0x65U, 0x63U, 0x74U, 0x20U, 0x73U, 0x69U, 0x7AU, 0x65U, 0x20U, 0x6FU, 0x66U, + 0x20U, 0x56U, 0x4FU, 0x54U, 0x45U, 0x20U, 0x64U, 0x61U, 0x74U, 0x61U, 0x20U, + 0x66U, 0x6FU, 0x72U, 0x20U, 0x54U, 0x4FU, 0x50U, 0x49U, 0x43U, 0x20U, 0x74U, + 0x79U, 0x70U, 0x65U, 0x2EU, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, + 0x64U, 0x61U, 0x74U, 0x61U, 0x5FU, 0x72U, 0x61U, 0x77U, 0x3AU, 0x00U, 0x74U, + 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x70U, 0x61U, 0x64U, 0x64U, 0x69U, 0x6EU, + 0x67U, 0x3AU, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x73U, 0x69U, + 0x7AU, 0x65U, 0x3AU, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x64U, + 0x61U, 0x74U, 0x61U, 0x3AU, 0x00U, 0x70U, 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, + 0x75U, 0x73U, 0x5FU, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x64U, 0x61U, + 0x74U, 0x61U, 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x64U, 0x61U, + 0x74U, 0x61U, 0x00U, 0x70U, 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, 0x75U, 0x73U, + 0x5FU, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, + 0x00U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, + 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, + 0x3AU, 0x20U, 0x59U, 0x6FU, 0x75U, 0x72U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, + 0x20U, 0x69U, 0x73U, 0x20U, 0x61U, 0x6CU, 0x72U, 0x65U, 0x61U, 0x64U, 0x79U, + 0x20U, 0x63U, 0x61U, 0x73U, 0x74U, 0x20U, 0x74U, 0x68U, 0x69U, 0x73U, 0x20U, + 0x77U, 0x61U, 0x79U, 0x20U, 0x66U, 0x6FU, 0x72U, 0x20U, 0x74U, 0x68U, 0x69U, + 0x73U, 0x20U, 0x74U, 0x6FU, 0x70U, 0x69U, 0x63U, 0x2EU, 0x00U, 0x74U, 0x6FU, + 0x70U, 0x69U, 0x63U, 0x5FU, 0x64U, 0x61U, 0x74U, 0x61U, 0x5FU, 0x7AU, 0x65U, + 0x72U, 0x6FU, 0x00U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x00U, 0x74U, 0x6FU, + 0x70U, 0x69U, 0x63U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, + 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x56U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x72U, + 0x65U, 0x63U, 0x6FU, 0x72U, 0x64U, 0x2EU, 0x20U, 0x4EU, 0x6FU, 0x74U, 0x20U, + 0x79U, 0x65U, 0x74U, 0x20U, 0x65U, 0x6EU, 0x6FU, 0x75U, 0x67U, 0x68U, 0x20U, + 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x20U, 0x74U, 0x6FU, 0x20U, 0x61U, 0x63U, + 0x74U, 0x69U, 0x6FU, 0x6EU, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, + 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4EU, 0x6FU, 0x74U, 0x20U, + 0x79U, 0x65U, 0x74U, 0x20U, 0x65U, 0x6EU, 0x6FU, 0x75U, 0x67U, 0x68U, 0x20U, + 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x20U, 0x74U, 0x6FU, 0x20U, 0x61U, 0x63U, + 0x74U, 0x69U, 0x6FU, 0x6EU, 0x20U, 0x4CU, 0x31U, 0x20U, 0x76U, 0x6FU, 0x74U, + 0x65U, 0x2EU, 0x2EU, 0x2EU, 0x00U, 0x22U, 0x41U, 0x63U, 0x74U, 0x69U, 0x6FU, + 0x6EU, 0x69U, 0x6EU, 0x67U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x22U, + 0x00U, 0x41U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, 0x6EU, 0x67U, 0x20U, + 0x76U, 0x6FU, 0x74U, 0x65U, 0x73U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, + 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x45U, 0x6DU, 0x69U, 0x74U, + 0x74U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x69U, 0x6EU, 0x76U, 0x6FU, 0x6BU, 0x65U, + 0x20U, 0x74U, 0x6FU, 0x20U, 0x4CU, 0x31U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, + 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x45U, 0x6DU, 0x69U, + 0x74U, 0x20U, 0x72U, 0x65U, 0x73U, 0x75U, 0x6CU, 0x74U, 0x00U, 0x47U, 0x6FU, + 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x53U, + 0x75U, 0x63U, 0x63U, 0x65U, 0x73U, 0x73U, 0x66U, 0x75U, 0x6CU, 0x6CU, 0x79U, + 0x20U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x74U, 0x65U, 0x64U, 0x20U, 0x4CU, 0x31U, + 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, + 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4CU, 0x31U, 0x20U, + 0x76U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x65U, 0x6DU, 0x69U, 0x73U, 0x73U, 0x69U, + 0x6FU, 0x6EU, 0x20U, 0x66U, 0x61U, 0x69U, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x00U, + 0x72U, 0x65U, 0x73U, 0x75U, 0x6CU, 0x74U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, + 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x52U, 0x65U, 0x77U, + 0x61U, 0x72U, 0x64U, 0x20U, 0x72U, 0x61U, 0x74U, 0x65U, 0x20U, 0x63U, 0x68U, + 0x61U, 0x6EU, 0x67U, 0x65U, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, + 0x65U, 0x64U, 0x21U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, + 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x52U, 0x65U, 0x77U, 0x61U, 0x72U, 0x64U, + 0x20U, 0x64U, 0x65U, 0x6CU, 0x61U, 0x79U, 0x20U, 0x63U, 0x68U, 0x61U, 0x6EU, + 0x67U, 0x65U, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x65U, 0x64U, + 0x21U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x61U, 0x6EU, 0x63U, 0x65U, + 0x3AU, 0x20U, 0x54U, 0x61U, 0x72U, 0x67U, 0x65U, 0x74U, 0x20U, 0x68U, 0x6FU, + 0x6FU, 0x6BU, 0x20U, 0x69U, 0x73U, 0x20U, 0x61U, 0x6CU, 0x72U, 0x65U, 0x61U, + 0x64U, 0x79U, 0x20U, 0x74U, 0x68U, 0x65U, 0x20U, 0x73U, 0x61U, 0x6DU, 0x65U, + 0x20U, 0x61U, 0x73U, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x65U, + 0x64U, 0x20U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, + 0x65U, 0x72U, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x48U, 0x6FU, 0x6FU, + 0x6BU, 0x20U, 0x48U, 0x61U, 0x73U, 0x68U, 0x20U, 0x64U, 0x6FU, 0x65U, 0x73U, + 0x6EU, 0x27U, 0x74U, 0x20U, 0x65U, 0x78U, 0x69U, 0x73U, 0x74U, 0x20U, 0x6FU, + 0x6EU, 0x20U, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x20U, 0x77U, 0x68U, + 0x69U, 0x6CU, 0x65U, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, + 0x6EU, 0x67U, 0x20U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x2EU, 0x00U, 0x45U, 0x6DU, + 0x69U, 0x74U, 0x74U, 0x65U, 0x64U, 0x54U, 0x78U, 0x6EU, 0x00U, 0x65U, 0x6DU, + 0x69U, 0x74U, 0x5FU, 0x72U, 0x65U, 0x73U, 0x75U, 0x6CU, 0x74U, 0x00U, 0x47U, + 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, + 0x45U, 0x6DU, 0x69U, 0x74U, 0x20U, 0x66U, 0x61U, 0x69U, 0x6CU, 0x65U, 0x64U, + 0x20U, 0x64U, 0x75U, 0x72U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x68U, 0x6FU, 0x6FU, + 0x6BU, 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, 0x6EU, 0x67U, + 0x2EU, 0x00U, 0x45U, 0x6DU, 0x69U, 0x74U, 0x74U, 0x65U, 0x64U, 0x54U, 0x78U, + 0x6EU, 0x48U, 0x61U, 0x73U, 0x68U, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, + 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x48U, 0x6FU, 0x6FU, 0x6BU, + 0x20U, 0x61U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x65U, 0x64U, 0x2EU, 0x00U, + 0x50U, 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, 0x75U, 0x73U, 0x20U, 0x70U, 0x72U, + 0x65U, 0x73U, 0x65U, 0x6EU, 0x74U, 0x3DU, 0x3DU, 0x3AU, 0x00U, 0x47U, 0x6FU, + 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x41U, + 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x69U, 0x6EU, 0x67U, 0x20U, 0x73U, 0x65U, + 0x61U, 0x74U, 0x20U, 0x63U, 0x68U, 0x61U, 0x6EU, 0x67U, 0x65U, 0x2CU, 0x20U, + 0x62U, 0x75U, 0x74U, 0x20U, 0x73U, 0x65U, 0x61U, 0x74U, 0x20U, 0x61U, 0x6CU, + 0x72U, 0x65U, 0x61U, 0x64U, 0x79U, 0x20U, 0x63U, 0x6FU, 0x6EU, 0x74U, 0x61U, + 0x69U, 0x6EU, 0x73U, 0x20U, 0x74U, 0x68U, 0x65U, 0x20U, 0x6EU, 0x65U, 0x77U, + 0x20U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x2EU, 0x00U, 0x47U, 0x6FU, + 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, 0x3AU, 0x20U, 0x4DU, + 0x6FU, 0x76U, 0x69U, 0x6EU, 0x67U, 0x20U, 0x65U, 0x78U, 0x69U, 0x73U, 0x74U, + 0x69U, 0x6EU, 0x67U, 0x20U, 0x6DU, 0x65U, 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, + 0x74U, 0x6FU, 0x20U, 0x6EU, 0x65U, 0x77U, 0x20U, 0x73U, 0x65U, 0x61U, 0x74U, + 0x2EU, 0x00U, 0x6FU, 0x70U, 0x00U, 0x45U, 0x00U, 0x5AU, 0x00U, 0x4DU, 0x00U, + 0x70U, 0x72U, 0x65U, 0x76U, 0x69U, 0x6FU, 0x75U, 0x73U, 0x5FU, 0x70U, 0x72U, + 0x65U, 0x73U, 0x65U, 0x6EU, 0x74U, 0x00U, 0x44U, 0x65U, 0x63U, 0x72U, 0x65U, + 0x6DU, 0x65U, 0x6EU, 0x74U, 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x63U, + 0x6FU, 0x75U, 0x6EU, 0x74U, 0x20U, 0x64U, 0x65U, 0x6CU, 0x65U, 0x74U, 0x65U, + 0x64U, 0x00U, 0x44U, 0x65U, 0x63U, 0x72U, 0x65U, 0x6DU, 0x65U, 0x6EU, 0x74U, + 0x20U, 0x76U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, + 0x20U, 0x74U, 0x6FU, 0x00U, 0x56U, 0x6FU, 0x74U, 0x65U, 0x20U, 0x65U, 0x6EU, + 0x74U, 0x72U, 0x79U, 0x20U, 0x64U, 0x65U, 0x6CU, 0x65U, 0x74U, 0x65U, 0x64U, + 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, 0x65U, + 0x3AU, 0x20U, 0x41U, 0x63U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x20U, 0x6DU, 0x65U, + 0x6DU, 0x62U, 0x65U, 0x72U, 0x20U, 0x63U, 0x68U, 0x61U, 0x6EU, 0x67U, 0x65U, + 0x2EU, 0x00U, 0x47U, 0x6FU, 0x76U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6EU, 0x63U, + 0x65U, 0x3AU, 0x20U, 0x49U, 0x6EU, 0x74U, 0x65U, 0x72U, 0x6EU, 0x61U, 0x6CU, + 0x20U, 0x6CU, 0x6FU, 0x67U, 0x69U, 0x63U, 0x20U, 0x65U, 0x72U, 0x72U, 0x6FU, + 0x72U, 0x2EU}; static const std::vector RewardHook = { 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x61U, 0x0EU, From 9651f68b2e544ccae913c92a1527799aff0e71b1 Mon Sep 17 00:00:00 2001 From: tequ Date: Tue, 28 Apr 2026 10:46:10 +0900 Subject: [PATCH 18/18] Add coverage workflow (#661) Co-authored-by: Bronek Kozicki Co-authored-by: Niq Dudfield --- .codecov.yml | 6 ++ .github/actions/xahau-ga-build/action.yml | 29 ++++-- .github/workflows/xahau-ga-nix.yml | 119 +++++++++++++++++++--- 3 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..191144aae --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,6 @@ +coverage: + status: + project: + default: + target: 60% + threshold: 2% diff --git a/.github/actions/xahau-ga-build/action.yml b/.github/actions/xahau-ga-build/action.yml index b4e8a6d11..242e5be19 100644 --- a/.github/actions/xahau-ga-build/action.yml +++ b/.github/actions/xahau-ga-build/action.yml @@ -2,6 +2,14 @@ name: build description: 'Builds the project with ccache integration' inputs: + cmake-target: + description: 'CMake target to build' + required: false + default: all + cmake-args: + description: 'Additional CMake arguments' + required: false + default: null generator: description: 'CMake generator to use' required: true @@ -20,6 +28,10 @@ inputs: description: 'C++ compiler to use' required: false default: '' + gcov: + description: 'Gcov to use' + required: false + default: '' compiler-id: description: 'Unique identifier: compiler-version-stdlib[-gccversion] (e.g. clang-14-libstdcxx-gcc11, gcc-13-libstdcxx)' required: false @@ -41,10 +53,11 @@ inputs: required: false default: 'dev' stdlib: - description: 'C++ standard library to use' + description: 'C++ standard library to use (default = compiler default, e.g. GCC always uses libstdc++)' required: true type: choice options: + - default - libstdcxx - libcxx clang_gcc_toolchain: @@ -87,11 +100,6 @@ runs: export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf" echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV - # Keep config separate from cache_dir so configs aren't swapped when CCACHE_DIR changes between steps - mkdir -p ~/.config/ccache - export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf" - echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV - # Configure ccache settings AFTER cache restore (prevents stale cached config) ccache --set-config=max_size=${{ inputs.ccache_max_size }} ccache --set-config=hash_dir=${{ inputs.ccache_hash_dir }} @@ -122,6 +130,10 @@ runs: export CXX="${{ inputs.cxx }}" fi + if [ -n "${{ inputs.gcov }}" ]; then + ln -sf /usr/bin/${{ inputs.gcov }} /usr/local/bin/gcov + fi + # Create wrapper toolchain that overlays ccache on top of Conan's toolchain # This enables ccache for the main app build without affecting Conan dependency builds if [ "${{ inputs.ccache_enabled }}" = "true" ]; then @@ -185,7 +197,8 @@ runs: -DCMAKE_TOOLCHAIN_FILE:FILEPATH=${TOOLCHAIN_FILE} \ -DCMAKE_BUILD_TYPE=${{ inputs.configuration }} \ -Dtests=TRUE \ - -Dxrpld=TRUE + -Dxrpld=TRUE \ + ${{ inputs.cmake-args }} - name: Show ccache config before build if: inputs.ccache_enabled == 'true' @@ -209,7 +222,7 @@ runs: VERBOSE_FLAG="-- -v" fi - cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) ${VERBOSE_FLAG} + cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) --target ${{ inputs.cmake-target }} ${VERBOSE_FLAG} - name: Show ccache statistics if: inputs.ccache_enabled == 'true' diff --git a/.github/workflows/xahau-ga-nix.yml b/.github/workflows/xahau-ga-nix.yml index efce25092..179081c05 100644 --- a/.github/workflows/xahau-ga-nix.yml +++ b/.github/workflows/xahau-ga-nix.yml @@ -57,8 +57,9 @@ jobs: "cc": "gcc-11", "cxx": "g++-11", "compiler_version": 11, - "stdlib": "libstdcxx", - "configuration": "Debug" + "stdlib": "default", + "configuration": "Debug", + "job_type": "build" }, { "compiler_id": "gcc-13-libstdcxx", @@ -66,8 +67,20 @@ jobs: "cc": "gcc-13", "cxx": "g++-13", "compiler_version": 13, - "stdlib": "libstdcxx", - "configuration": "Debug" + "stdlib": "default", + "configuration": "Debug", + "job_type": "build" + }, + { + "compiler_id": "gcc-13-libstdcxx", + "compiler": "gcc", + "cc": "gcc-13", + "cxx": "g++-13", + "gcov": "gcov-13", + "compiler_version": 13, + "stdlib": "default", + "configuration": "Debug", + "job_type": "coverage" }, { "compiler_id": "clang-14-libstdcxx-gcc11", @@ -77,7 +90,8 @@ jobs: "compiler_version": 14, "stdlib": "libstdcxx", "clang_gcc_toolchain": 11, - "configuration": "Debug" + "configuration": "Debug", + "job_type": "build" }, { "compiler_id": "clang-16-libstdcxx-gcc13", @@ -87,7 +101,8 @@ jobs: "compiler_version": 16, "stdlib": "libstdcxx", "clang_gcc_toolchain": 13, - "configuration": "Debug" + "configuration": "Debug", + "job_type": "build" }, { "compiler_id": "clang-17-libcxx", @@ -96,7 +111,8 @@ jobs: "cxx": "clang++-17", "compiler_version": 17, "stdlib": "libcxx", - "configuration": "Debug" + "configuration": "Debug", + "job_type": "build" }, { # Clang 18 - testing if it's faster than Clang 17 with libc++ @@ -107,14 +123,16 @@ jobs: "cxx": "clang++-18", "compiler_version": 18, "stdlib": "libcxx", - "configuration": "Debug" + "configuration": "Debug", + "job_type": "build" } ] # Minimal matrix for PRs and feature branches minimal_matrix = [ full_matrix[1], # gcc-13 (middle-ground gcc) - full_matrix[2] # clang-14 (mature, stable clang) + full_matrix[2], # gcc-13 coverage + full_matrix[3] # clang-14 (mature, stable clang) ] # Determine which matrix to use based on the target branch @@ -189,14 +207,21 @@ jobs: # Select the appropriate matrix if use_full: if force_full: - print(f"Using FULL matrix (6 configs) - forced by [ci-nix-full-matrix] tag") + print(f"Using FULL matrix (7 configs) - forced by [ci-nix-full-matrix] tag") else: - print(f"Using FULL matrix (6 configs) - targeting main branch") + print(f"Using FULL matrix (7 configs) - targeting main branch") matrix = full_matrix else: - print(f"Using MINIMAL matrix (2 configs) - feature branch/PR") + print(f"Using MINIMAL matrix (3 configs) - feature branch/PR") matrix = minimal_matrix - + + # Add runs_on based on job_type + for entry in matrix: + if entry.get("job_type") == "coverage": + entry["runs_on"] = '["self-hosted", "generic", 24.04]' + else: + entry["runs_on"] = '["self-hosted", "generic", 20.04]' + # Output the matrix as JSON output = json.dumps({"include": matrix}) with open(os.environ['GITHUB_OUTPUT'], 'a') as f: @@ -204,7 +229,7 @@ jobs: build: needs: matrix-setup - runs-on: [self-hosted, generic, 20.04] + runs-on: ${{ fromJSON(matrix.runs_on) }} container: image: ubuntu:24.04 volumes: @@ -233,7 +258,7 @@ jobs: apt-get install -y software-properties-common add-apt-repository ppa:ubuntu-toolchain-r/test -y apt-get update - apt-get install -y python3 python-is-python3 pipx + apt-get install -y git python3 python-is-python3 pipx pipx ensurepath apt-get install -y cmake ninja-build ${{ matrix.cc }} ${{ matrix.cxx }} ccache apt-get install -y perl # for openssl build @@ -304,6 +329,12 @@ jobs: pipx install "conan>=2.0,<3" echo "$HOME/.local/bin" >> $GITHUB_PATH + # Install gcovr for coverage jobs + if [ "${{ matrix.job_type }}" = "coverage" ]; then + pipx install "gcovr>=7,<9" + apt-get install -y lcov + fi + - name: Check environment run: | echo "PATH:" @@ -313,6 +344,13 @@ jobs: which ${{ matrix.cc }} && ${{ matrix.cc }} --version || echo "${{ matrix.cc }} not found" which ${{ matrix.cxx }} && ${{ matrix.cxx }} --version || echo "${{ matrix.cxx }} not found" which ccache && ccache --version || echo "ccache not found" + + # Check gcovr for coverage jobs + if [ "${{ matrix.job_type }}" = "coverage" ]; then + which gcov && gcov --version || echo "gcov not found" + which gcovr && gcovr --version || echo "gcovr not found" + fi + echo "---- Full Environment ----" env @@ -340,6 +378,7 @@ jobs: gha_cache_enabled: 'false' # Disable caching for self hosted runner - name: Build + if: matrix.job_type == 'build' uses: ./.github/actions/xahau-ga-build with: generator: Ninja @@ -354,7 +393,26 @@ jobs: clang_gcc_toolchain: ${{ matrix.clang_gcc_toolchain || '' }} ccache_max_size: '100G' + - name: Build (Coverage) + if: matrix.job_type == 'coverage' + uses: ./.github/actions/xahau-ga-build + with: + generator: Ninja + configuration: ${{ matrix.configuration }} + build_dir: ${{ env.build_dir }} + cc: ${{ matrix.cc }} + cxx: ${{ matrix.cxx }} + gcov: ${{ matrix.gcov }} + compiler-id: ${{ matrix.compiler_id }} + cache_version: ${{ env.CACHE_VERSION }} + main_branch: ${{ env.MAIN_BRANCH_NAME }} + stdlib: ${{ matrix.stdlib }} + cmake-args: '-Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS="-O0" -DCMAKE_C_FLAGS="-O0"' + cmake-target: 'coverage' + ccache_max_size: '100G' + - name: Set artifact name + if: matrix.job_type == 'build' id: set-artifact-name run: | ARTIFACT_NAME="build-output-nix-${{ github.run_id }}-${{ matrix.compiler }}-${{ matrix.configuration }}" @@ -367,6 +425,7 @@ jobs: ls -la ${{ env.build_dir }} || echo "Build directory not found or empty" - name: Run tests + if: matrix.job_type == 'build' run: | # Ensure the binary exists before trying to run if [ -f "${{ env.build_dir }}/rippled" ]; then @@ -375,3 +434,33 @@ jobs: echo "Error: rippled executable not found in ${{ env.build_dir }}" exit 1 fi + + # Coverage-specific steps + - name: Move coverage report + if: matrix.job_type == 'coverage' + shell: bash + run: | + mv "${{ env.build_dir }}/coverage.xml" ./ + + - name: Archive coverage report + if: matrix.job_type == 'coverage' + uses: actions/upload-artifact@v4 + with: + name: coverage.xml + path: coverage.xml + retention-days: 30 + + - name: Upload coverage report + if: matrix.job_type == 'coverage' + uses: wandalen/wretry.action/main@v3 + with: + action: codecov/codecov-action@v4.3.0 + with: | + files: coverage.xml + fail_ci_if_error: true + disable_search: true + verbose: true + plugin: noop + token: ${{ secrets.CODECOV_TOKEN }} + attempt_limit: 5 + attempt_delay: 210000 # in milliseconds