From e57d0689ccef31f452b56edf776806ee273e2a1b Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Fri, 2 Aug 2024 11:24:45 -0400 Subject: [PATCH 01/37] Implement Deep Freeze Specification: https://github.com/XRPLF/XRPL-Standards/discussions/220 --- include/xrpl/protocol/Feature.h | 2 +- include/xrpl/protocol/LedgerFormats.h | 2 + include/xrpl/protocol/TxFlags.h | 4 +- include/xrpl/protocol/detail/features.macro | 1 + src/test/app/AMMExtended_test.cpp | 208 ++++++++++++++++++++ src/test/app/AMM_test.cpp | 43 ++++ src/test/app/Check_test.cpp | 67 +++++++ src/test/app/Freeze_test.cpp | 85 ++++++-- src/xrpld/app/paths/detail/StepChecks.h | 7 + src/xrpld/app/tx/detail/SetTrust.cpp | 19 ++ src/xrpld/ledger/detail/View.cpp | 29 ++- 11 files changed, 449 insertions(+), 18 deletions(-) diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index eb975f39ae0..a2510c63000 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 = 80; +static constexpr std::size_t numFeatures = 81; /** 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/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index b0374db1c29..7359fc192df 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -164,6 +164,8 @@ enum LedgerSpecificFlags { lsfHighFreeze = 0x00800000, // True, high side has set freeze flag lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client // apps to identify payments via AMM. + lsfLowDeepFreeze = 0x02000000, + lsfHighDeepFreeze = 0x04000000, // ltSIGNER_LIST lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 4894f48a7f9..2e230595081 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -114,9 +114,11 @@ constexpr std::uint32_t tfSetNoRipple = 0x00020000; constexpr std::uint32_t tfClearNoRipple = 0x00040000; constexpr std::uint32_t tfSetFreeze = 0x00100000; constexpr std::uint32_t tfClearFreeze = 0x00200000; +constexpr std::uint32_t tfSetDeepFreeze = 0x00400000; +constexpr std::uint32_t tfClearDeepFreeze = 0x00800000; constexpr std::uint32_t tfTrustSetMask = ~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze | - tfClearFreeze); + tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze); // EnableAmendment flags: constexpr std::uint32_t tfGotMajority = 0x00010000; diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 3a8d77e2bab..263f95575f9 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -95,6 +95,7 @@ XRPL_FIX (1513, Supported::yes, VoteBehavior::DefaultYe XRPL_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo) +XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 96053b93b44..52010950772 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -3615,6 +3615,208 @@ struct AMMExtended_test : public jtx::AMMTest } } + void + testRippleDeepState(FeatureBitset features) + { + testcase("RippleState Deep Freeze"); + + using namespace test::jtx; + Env env(*this, features); + + Account const G1{"G1"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1'000), G1, alice, bob); + env.close(); + + env.trust(G1["USD"](100), bob); + env.trust(G1["USD"](205), alice); + env.close(); + + env(pay(G1, bob, G1["USD"](10))); + env(pay(G1, alice, G1["USD"](205))); + env.close(); + + AMM ammAlice(env, alice, XRP(500), G1["USD"](105)); + + { + auto lines = getAccountLines(env, bob); + if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u))) + return; + BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human()); + BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100"); + BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10"); + } + + { + auto lines = getAccountLines(env, alice, G1["USD"]); + if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u))) + return; + BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human()); + BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205"); + // 105 transferred to AMM + BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100"); + } + + { + // Account with line unfrozen (proving operations normally work) + // test: can make Payment on that line + env(pay(alice, bob, G1["USD"](1))); + + // test: can receive Payment on that line + env(pay(bob, alice, G1["USD"](1))); + env.close(); + } + + { + // Is created via a TrustSet with SetFreeze flag + // test: sets LowFreeze | HighFreeze flags + env(trust(G1, bob["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = + affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfLowLimit.fieldName] == + G1["USD"](0).value().getJson(JsonOptions::none)); + if (features[featureDeepFreeze]) + { + BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowDeepFreeze); + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); + } + else + { + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowDeepFreeze)); + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); + } + env.close(); + } + + { + // Account with line deep frozen by issuer + // test: can not buy more assets on that line + env(offer(bob, G1["USD"](5), XRP(25))); + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 4u))) + return; + auto ff = + affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15} + .value() + .getJson(JsonOptions::none); + if (features[featureDeepFreeze]) + { + BEAST_EXPECT(ff[sfHighLimit.fieldName] == Json::nullValue); + BEAST_EXPECT(ff[sfBalance.fieldName] == Json::nullValue); + } + else + { + BEAST_EXPECT( + ff[sfHighLimit.fieldName] == + bob["USD"](100).value().getJson(JsonOptions::none)); + BEAST_EXPECT(ff[sfBalance.fieldName] == amt); + } + env.close(); + if (features[featureDeepFreeze]) + { + BEAST_EXPECT(ammAlice.expectBalances( + XRP(500), G1["USD"](105), ammAlice.tokens())); + } + else + { + BEAST_EXPECT(ammAlice.expectBalances( + XRP(525), G1["USD"](100), ammAlice.tokens())); + } + } + + { + // test: can not sell assets from that line + env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER)); + + // test: can not make Payment from that line + env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY)); + + if (features[featureDeepFreeze]) + { + // test: can not receive Payment on that line + env(pay(alice, bob, G1["USD"](1)), ter(tecPATH_DRY)); + } + else + { + // test: can receive Payment on that line + env(pay(alice, bob, G1["USD"](1))); + } + } + + { + // check G1 account lines + // test: shows deep freeze + auto lines = getAccountLines(env, G1); + Json::Value bobLine; + for (auto const& it : lines[jss::lines]) + { + if (it[jss::account] == bob.human()) + { + bobLine = it; + break; + } + } + if (!BEAST_EXPECT(bobLine)) + return; + BEAST_EXPECT(bobLine[jss::freeze] == true); + if (features[featureDeepFreeze]) + BEAST_EXPECT(bobLine[jss::balance] == "-10"); + else + BEAST_EXPECT(bobLine[jss::balance] == "-16"); + } + + { + // test: shows deep freeze peer + auto lines = getAccountLines(env, bob); + Json::Value g1Line; + for (auto const& it : lines[jss::lines]) + { + if (it[jss::account] == G1.human()) + { + g1Line = it; + break; + } + } + if (!BEAST_EXPECT(g1Line)) + return; + BEAST_EXPECT(g1Line[jss::freeze_peer] == true); + if (features[featureDeepFreeze]) + BEAST_EXPECT(g1Line[jss::balance] == "10"); + else + BEAST_EXPECT(g1Line[jss::balance] == "16"); + } + + { + // Is cleared via a TrustSet with ClearDeepFreeze flag + // test: sets LowDeepFreeze | HighDeepFreeze flags + env(trust(G1, bob["USD"](0), tfClearDeepFreeze)); + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!features[featureDeepFreeze] && + BEAST_EXPECT(checkArraySize(affected, 1u))) + return; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = + affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfLowLimit.fieldName] == + G1["USD"](0).value().getJson(JsonOptions::none)); + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowDeepFreeze)); + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); + env.close(); + } + } + void testGlobalFreeze(FeatureBitset features) { @@ -4129,7 +4331,13 @@ struct AMMExtended_test : public jtx::AMMTest { using namespace test::jtx; auto const sa = supported_amendments(); + testRippleState(sa - featureDeepFreeze); + testRippleDeepState(sa - featureDeepFreeze); + testGlobalFreeze(sa - featureDeepFreeze); + testOffersWhenFrozen(sa - featureDeepFreeze); + testRippleState(sa); + testRippleDeepState(sa); testGlobalFreeze(sa); testOffersWhenFrozen(sa); } diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index ceddc019504..0320a0cbf74 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -4397,6 +4397,48 @@ struct AMM_test : public jtx::AMMTest 0, std::nullopt, {features}); + + // Individually deep frozen account + if (features[featureDeepFreeze]) + { + testAMM( + [&](AMM& ammAlice, Env& env) { + env(trust( + gw, carol["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust( + gw, alice["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + env(pay(alice, carol, USD(1)), + path(~USD), + sendmax(XRP(10)), + txflags(tfNoRippleDirect | tfPartialPayment), + ter(tecPATH_DRY)); + }, + std::nullopt, + 0, + std::nullopt, + {features}); + } + else + { + testAMM( + [&](AMM& ammAlice, Env& env) { + env(trust( + gw, carol["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust( + gw, alice["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + env(pay(alice, carol, USD(1)), + path(~USD), + sendmax(XRP(10)), + txflags(tfNoRippleDirect | tfPartialPayment), + ter(tesSUCCESS)); + }, + std::nullopt, + 0, + std::nullopt, + {features}); + } } void @@ -6882,6 +6924,7 @@ struct AMM_test : public jtx::AMMTest testBasicPaymentEngine(all - fixAMMv1_1); testBasicPaymentEngine(all - fixReducedOffersV2); testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2); + testBasicPaymentEngine(all - featureDeepFreeze); testAMMTokens(); testAmendment(); testFlags(); diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 31b45abf43a..9739cf36c63 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -546,6 +546,72 @@ class Check_test : public beast::unit_test::suite env(trust(alice, USD(0), tfClearFreeze)); env.close(); } + { + // Deep Frozen trust line. Check creation should be similar to + // payment behavior in the face of frozen trust lines. + env.trust(USD(1000), alice); + env.trust(USD(1000), bob); + env.close(); + env(pay(gw1, alice, USD(25))); + env(pay(gw1, bob, USD(25))); + env.close(); + + // Setting trustline deep freeze in one direction prevents alice + // from creating a check for USD. And bob and gw1 should not be + // able to create a check for USD to alice. + env(trust(gw1, alice["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + env(check::create(alice, bob, USD(50)), ter(tecFROZEN)); + env.close(); + env(pay(alice, bob, USD(1)), ter(tecPATH_DRY)); + env.close(); + env(check::create(bob, alice, USD(50))); + env.close(); + if (features[featureDeepFreeze]) + env(pay(bob, alice, USD(1)), ter(tecPATH_DRY)); + else + env(pay(bob, alice, USD(1))); + env.close(); + env(check::create(gw1, alice, USD(50))); + env.close(); + env(pay(gw1, alice, USD(1))); + env.close(); + + // Clear that freeze. Now check creation works. + env(trust(gw1, alice["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + env(check::create(alice, bob, USD(50))); + env.close(); + env(check::create(bob, alice, USD(50))); + env.close(); + env(check::create(gw1, alice, USD(50))); + env.close(); + + // Deep Freezing in the other direction does effect alice's USD + // check creation, and prevents bob and gw1 from writing a check + // for USD to alice. + env(trust(alice, USD(0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + env(check::create(alice, bob, USD(50))); + env.close(); + if (features[featureDeepFreeze]) + env(pay(alice, bob, USD(1)), ter(tecPATH_DRY)); + else + env(pay(alice, bob, USD(1))); + env.close(); + env(check::create(bob, alice, USD(50)), ter(tecFROZEN)); + env.close(); + env(pay(bob, alice, USD(1)), ter(tecPATH_DRY)); + env.close(); + env(check::create(gw1, alice, USD(50)), ter(tecFROZEN)); + env.close(); + env(pay(gw1, alice, USD(1)), ter(tecPATH_DRY)); + env.close(); + + // Clear that deep freeze. + env(trust(alice, USD(0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } // Expired expiration. env(check::create(alice, bob, USD(50)), @@ -2719,6 +2785,7 @@ class Check_test : public beast::unit_test::suite auto const sa = supported_amendments(); testWithFeats(sa - featureCheckCashMakesTrustLine); testWithFeats(sa - disallowIncoming); + testWithFeats(sa - featureDeepFreeze); testWithFeats(sa); testTrustLineCreation(sa); // Test with featureCheckCashMakesTrustLine diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 0c54f0e1f39..2e208cbe855 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -99,23 +99,67 @@ class Freeze_test : public beast::unit_test::suite env.close(); } + if (features[featureDeepFreeze]) + { + { + // Is created via a TrustSet with SetDeepFreeze flag + // but not tfSetFreeze + // test: does not set LowDeepFreeze | HighDeepFreeze flags + env(trust(G1, bob["USD"](0), tfSetDeepFreeze)); + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + BEAST_EXPECT(checkArraySize(affected, 1u)); + auto ff = affected[0u][sfModifiedNode.fieldName] + [sfFinalFields.fieldName]; + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowDeepFreeze)); + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); + env.close(); + } + + { + // Is created via a TrustSet with SetDeepFreeze flag + // and tfSetFreeze + // test: sets LowDeepFreeze | HighDeepFreeze flags + env(trust(G1, bob["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = affected[1u][sfModifiedNode.fieldName] + [sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[sfLowLimit.fieldName] == + G1["USD"](0).value().getJson(JsonOptions::none)); + BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowDeepFreeze); + BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); + env.close(); + } + } + { // Account with line frozen by issuer // test: can buy more assets on that line env(offer(bob, G1["USD"](5), XRP(25))); auto affected = env.meta()->getJson( JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 5u))) - return; + BEAST_EXPECT( + checkArraySize(affected, 4u) || checkArraySize(affected, 5u)); auto ff = affected[3u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; - BEAST_EXPECT( - ff[sfHighLimit.fieldName] == - bob["USD"](100).value().getJson(JsonOptions::none)); - auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15} - .value() - .getJson(JsonOptions::none); - BEAST_EXPECT(ff[sfBalance.fieldName] == amt); + if (affected.size() == 5) + { + BEAST_EXPECT( + ff[sfHighLimit.fieldName] == + bob["USD"](100).value().getJson(JsonOptions::none)); + auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15} + .value() + .getJson(JsonOptions::none); + BEAST_EXPECT(ff[sfBalance.fieldName] == amt); + } + else if (affected.size() == 4) + { + BEAST_EXPECT(ff[sfHighLimit.fieldName] == Json::nullValue); + } env.close(); } @@ -123,8 +167,11 @@ class Freeze_test : public beast::unit_test::suite // test: can not sell assets from that line env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER)); - // test: can receive Payment on that line - env(pay(alice, bob, G1["USD"](1))); + // test: can receive Payment on that line unless deep frozen + if (features[featureDeepFreeze]) + env(pay(alice, bob, G1["USD"](1)), ter(tecPATH_DRY)); + else + env(pay(alice, bob, G1["USD"](1))); // test: can not make Payment from that line env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY)); @@ -146,7 +193,12 @@ class Freeze_test : public beast::unit_test::suite if (!BEAST_EXPECT(bobLine)) return; BEAST_EXPECT(bobLine[jss::freeze] == true); - BEAST_EXPECT(bobLine[jss::balance] == "-16"); + if (features[featureDeepFreeze] && features[featureFlowCross]) + BEAST_EXPECT(bobLine[jss::balance] == "-10"); + else if (features[featureFlowCross]) + BEAST_EXPECT(bobLine[jss::balance] == "-16"); + else if (features[featureDeepFreeze]) + BEAST_EXPECT(bobLine[jss::balance] == "-15"); } { @@ -164,9 +216,13 @@ class Freeze_test : public beast::unit_test::suite if (!BEAST_EXPECT(g1Line)) return; BEAST_EXPECT(g1Line[jss::freeze_peer] == true); - BEAST_EXPECT(g1Line[jss::balance] == "16"); + if (features[featureDeepFreeze] && features[featureFlowCross]) + BEAST_EXPECT(g1Line[jss::balance] == "10"); + else if (features[featureFlowCross]) + BEAST_EXPECT(g1Line[jss::balance] == "16"); + else if (features[featureDeepFreeze]) + BEAST_EXPECT(g1Line[jss::balance] == "15"); } - { // Is cleared via a TrustSet with ClearFreeze flag // test: sets LowFreeze | HighFreeze flags @@ -519,6 +575,7 @@ class Freeze_test : public beast::unit_test::suite using namespace test::jtx; auto const sa = supported_amendments(); testAll(sa - featureFlowCross); + testAll(sa - featureDeepFreeze); testAll(sa); } }; diff --git a/src/xrpld/app/paths/detail/StepChecks.h b/src/xrpld/app/paths/detail/StepChecks.h index 140c9d1fe46..10e819e9116 100644 --- a/src/xrpld/app/paths/detail/StepChecks.h +++ b/src/xrpld/app/paths/detail/StepChecks.h @@ -52,6 +52,13 @@ checkFreeze( { return terNO_LINE; } + if (view.rules().enabled(featureDeepFreeze)) + { + if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)) + { + return terNO_LINE; + } + } } return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 954fc6543f1..9421990f5e0 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -242,6 +242,11 @@ SetTrust::doApply() bool const bClearNoRipple = (uTxFlags & tfClearNoRipple); bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); + bool const bDeepFreezeEnabled = view().rules().enabled(featureDeepFreeze); + bool const bSetDeepFreeze = + bDeepFreezeEnabled && (uTxFlags & tfSetDeepFreeze); + bool const bClearDeepFreeze = + bDeepFreezeEnabled && (uTxFlags & tfClearDeepFreeze); auto viewJ = ctx_.app.journal("View"); @@ -408,15 +413,29 @@ SetTrust::doApply() uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } + bool willSetFreeze = false; if (bSetFreeze && !bClearFreeze && !sle->isFlag(lsfNoFreeze)) { uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); + willSetFreeze = true; } else if (bClearFreeze && !bSetFreeze) { uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); } + bool const alreadyFrozen = + bHigh ? sle->isFlag(lsfHighFreeze) : sle->isFlag(lsfLowFreeze); + if (bSetDeepFreeze && !bClearDeepFreeze && + (willSetFreeze || alreadyFrozen)) + { + uFlagsOut |= (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + } + else if (bClearDeepFreeze && !bSetDeepFreeze) + { + uFlagsOut &= ~(bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + } + if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index ae4eb095017..2117bf21dd8 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -265,6 +265,30 @@ isFrozen( isIndividualFrozen(view, account, mptIssue); } +bool +isDeepFrozen( + ReadView const& view, + AccountID const& account, + Currency const& currency, + AccountID const& issuer) +{ + if (!view.rules().enabled(featureDeepFreeze)) + return false; + if (isXRP(currency)) + return false; + if (issuer != account) + { + // Check if the issuer deep froze the line + auto sle = view.read(keylet::line(account, issuer, currency)); + if (sle) + { + if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)) + return true; + } + } + return false; +} + STAmount accountHolds( ReadView const& view, @@ -287,8 +311,9 @@ accountHolds( amount.clear(Issue{currency, issuer}); } else if ( - (zeroIfFrozen == fhZERO_IF_FROZEN) && - isFrozen(view, account, currency, issuer)) + ((zeroIfFrozen == fhZERO_IF_FROZEN) && + isFrozen(view, account, currency, issuer)) || + isDeepFrozen(view, account, currency, issuer)) { amount.clear(Issue{currency, issuer}); } From a0d48ae9ddaf14de298cda542570c0739858d085 Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Thu, 31 Oct 2024 12:58:50 -0400 Subject: [PATCH 02/37] [FOLD] Move feature to top of list --- include/xrpl/protocol/detail/features.macro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 263f95575f9..6b6c1a742fd 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -31,6 +31,7 @@ // InvariantsV1_1 will be changes to Supported::yes when all the // invariants expected to be included under it are complete. +XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(InvariantsV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (NFTokenPageLinks, Supported::yes, VoteBehavior::DefaultNo) @@ -95,7 +96,6 @@ XRPL_FIX (1513, Supported::yes, VoteBehavior::DefaultYe XRPL_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo) -XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. From 4aac190a6abf9c56d10c11b1ec2d06d40191f8e7 Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Thu, 31 Oct 2024 14:25:18 -0400 Subject: [PATCH 03/37] [FOLD] Make use of the deep freeze flags malformed prior to accepting the amendment. --- src/xrpld/app/tx/detail/SetTrust.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 9421990f5e0..5ea86e88d79 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -429,11 +429,17 @@ SetTrust::doApply() if (bSetDeepFreeze && !bClearDeepFreeze && (willSetFreeze || alreadyFrozen)) { - uFlagsOut |= (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + if (ctx_.view().rules().enabled(featureDeepFreeze)) + uFlagsOut |= (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + else + return temMALFORMED; } else if (bClearDeepFreeze && !bSetDeepFreeze) { - uFlagsOut &= ~(bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + if (ctx_.view().rules().enabled(featureDeepFreeze)) + uFlagsOut &= ~(bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + else + return temMALFORMED; } if (QUALITY_ONE == uLowQualityOut) From 306a11248815d42840e6bdd11c2f632b91893a9f Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 15 Nov 2024 14:08:25 +0000 Subject: [PATCH 04/37] Fixed creation of buy offer transaction to fail with tecFROZEN instantly --- src/test/app/AMMExtended_test.cpp | 28 ++----------------------- src/xrpld/app/tx/detail/CreateOffer.cpp | 27 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 52010950772..770a673aa2e 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -3698,39 +3698,15 @@ struct AMMExtended_test : public jtx::AMMTest { // Account with line deep frozen by issuer // test: can not buy more assets on that line - env(offer(bob, G1["USD"](5), XRP(25))); - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 4u))) - return; - auto ff = - affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; - auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15} - .value() - .getJson(JsonOptions::none); if (features[featureDeepFreeze]) { - BEAST_EXPECT(ff[sfHighLimit.fieldName] == Json::nullValue); - BEAST_EXPECT(ff[sfBalance.fieldName] == Json::nullValue); + env(offer(bob, G1["USD"](5), XRP(25)), ter(tecFROZEN)); } else { - BEAST_EXPECT( - ff[sfHighLimit.fieldName] == - bob["USD"](100).value().getJson(JsonOptions::none)); - BEAST_EXPECT(ff[sfBalance.fieldName] == amt); + env(offer(bob, G1["USD"](5), XRP(25))); } env.close(); - if (features[featureDeepFreeze]) - { - BEAST_EXPECT(ammAlice.expectBalances( - XRP(500), G1["USD"](105), ammAlice.tokens())); - } - else - { - BEAST_EXPECT(ammAlice.expectBalances( - XRP(525), G1["USD"](100), ammAlice.tokens())); - } } { diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 2a5145594a1..831b0fcf15a 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -257,6 +257,33 @@ CreateOffer::checkAcceptAsset( } } + if (view.rules().enabled(featureDeepFreeze)) + { + // An issuer can always accept its own issuance. + if (issue.account == id) + { + return tesSUCCESS; + } + + auto const trustLine = + view.read(keylet::line(id, issue.account, issue.currency)); + + if (!trustLine) + { + return tesSUCCESS; + } + + // There's no difference which side enacted deep freeze, accepting + // tokens shouldn't be possible. + bool const deepFrozen = + (*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze); + + if (deepFrozen) + { + return tecFROZEN; + } + } + return tesSUCCESS; } From f5efa317c0a42d5a01630bb09ba8a15f398b7566 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 18 Nov 2024 18:59:26 +0000 Subject: [PATCH 05/37] Fixed comments #1 --- src/xrpld/app/paths/detail/StepChecks.h | 2 + src/xrpld/app/tx/detail/CreateOffer.cpp | 4 +- src/xrpld/app/tx/detail/SetTrust.cpp | 47 ++++++++++++-------- src/xrpld/ledger/detail/View.cpp | 58 ++++++++++++++++--------- 4 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/xrpld/app/paths/detail/StepChecks.h b/src/xrpld/app/paths/detail/StepChecks.h index 10e819e9116..d9ce2cc9d34 100644 --- a/src/xrpld/app/paths/detail/StepChecks.h +++ b/src/xrpld/app/paths/detail/StepChecks.h @@ -54,6 +54,8 @@ checkFreeze( } if (view.rules().enabled(featureDeepFreeze)) { + // Unline normal freeze, a deep frozen trust line acts the same + // regardless of which side froze it if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)) { return terNO_LINE; diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 831b0fcf15a..2665851767f 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -259,7 +259,9 @@ CreateOffer::checkAcceptAsset( if (view.rules().enabled(featureDeepFreeze)) { - // An issuer can always accept its own issuance. + // An account can not create a trustline to itself, so no line can exist + // to be frozen. Additionally, an issuer can always accept its own + // issuance. if (issue.account == id) { return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 5ea86e88d79..6335bd36e79 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -45,6 +45,16 @@ SetTrust::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } + if (!ctx.rules.enabled(featureDeepFreeze)) + { + // Even though the deep freeze flags are included in the + // `tfTrustSetMask`, they are not valid if the amendment is not enabled. + if (uTxFlags & (tfSetDeepFreeze | tfClearDeepFreeze)) + { + return temINVALID_FLAG; + } + } + STAmount const saLimitAmount(tx.getFieldAmount(sfLimitAmount)); if (!isLegalNet(saLimitAmount)) @@ -242,11 +252,8 @@ SetTrust::doApply() bool const bClearNoRipple = (uTxFlags & tfClearNoRipple); bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); - bool const bDeepFreezeEnabled = view().rules().enabled(featureDeepFreeze); - bool const bSetDeepFreeze = - bDeepFreezeEnabled && (uTxFlags & tfSetDeepFreeze); - bool const bClearDeepFreeze = - bDeepFreezeEnabled && (uTxFlags & tfClearDeepFreeze); + bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze); + bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze); auto viewJ = ctx_.app.journal("View"); @@ -413,33 +420,35 @@ SetTrust::doApply() uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } - bool willSetFreeze = false; if (bSetFreeze && !bClearFreeze && !sle->isFlag(lsfNoFreeze)) { uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); - willSetFreeze = true; } else if (bClearFreeze && !bSetFreeze) { uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); } - bool const alreadyFrozen = - bHigh ? sle->isFlag(lsfHighFreeze) : sle->isFlag(lsfLowFreeze); - if (bSetDeepFreeze && !bClearDeepFreeze && - (willSetFreeze || alreadyFrozen)) + if (bSetDeepFreeze && !bClearDeepFreeze && !sle->isFlag(lsfNoFreeze)) { - if (ctx_.view().rules().enabled(featureDeepFreeze)) - uFlagsOut |= (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); - else - return temMALFORMED; + uFlagsOut |= (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); } else if (bClearDeepFreeze && !bSetDeepFreeze) { - if (ctx_.view().rules().enabled(featureDeepFreeze)) - uFlagsOut &= ~(bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); - else - return temMALFORMED; + uFlagsOut &= ~(bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + } + + // Checking freeze invariants + auto const frozen = uFlagsOut & (bHigh ? lsfHighFreeze : lsfLowFreeze); + auto const deepFrozen = + uFlagsOut & (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + + // Trying to set deep freeze on not already frozen trust line must fail + // This also checks that clearing normal freeze while deep frozen must + // not work + if (deepFrozen && !frozen) + { + return tecNO_PERMISSION; } if (QUALITY_ONE == uLowQualityOut) diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 2117bf21dd8..caeacc14574 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -273,20 +273,24 @@ isDeepFrozen( AccountID const& issuer) { if (!view.rules().enabled(featureDeepFreeze)) + { return false; + } + if (isXRP(currency)) + { return false; - if (issuer != account) + } + + if (issuer == account) { - // Check if the issuer deep froze the line - auto sle = view.read(keylet::line(account, issuer, currency)); - if (sle) - { - if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)) - return true; - } + return false; } - return false; + + // Check if the line is deep frozen + auto const sle = view.read(keylet::line(account, issuer, currency)); + return sle && + (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)); } STAmount @@ -306,18 +310,25 @@ accountHolds( // IOU: Return balance on trust line modulo freeze auto const sle = view.read(keylet::line(account, issuer, currency)); - if (!sle) - { - amount.clear(Issue{currency, issuer}); - } - else if ( - ((zeroIfFrozen == fhZERO_IF_FROZEN) && - isFrozen(view, account, currency, issuer)) || - isDeepFrozen(view, account, currency, issuer)) - { - amount.clear(Issue{currency, issuer}); - } - else + auto const allowBalance = [&]() { + if (!sle) + { + return false; + } + + if (zeroIfFrozen == fhZERO_IF_FROZEN) + { + if (isFrozen(view, account, currency, issuer) || + isDeepFrozen(view, account, currency, issuer)) + { + return false; + } + } + + return true; + }(); + + if (allowBalance) { amount = sle->getFieldAmount(sfBalance); if (account > issuer) @@ -327,6 +338,11 @@ accountHolds( } amount.setIssuer(issuer); } + else + { + amount.clear(Issue{currency, issuer}); + } + JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account) << " amount=" << amount.getFullText(); From 8e1977f2274844fe00dc34f9768c7bfade024a70 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Wed, 20 Nov 2024 15:10:30 +0000 Subject: [PATCH 06/37] Reworked freeze flag handling in SetTrust --- src/test/app/Freeze_test.cpp | 293 ++++++++++++++++++++------- src/xrpld/app/tx/detail/SetTrust.cpp | 159 +++++++++++---- 2 files changed, 332 insertions(+), 120 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 2e208cbe855..eb87f775107 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -99,67 +99,23 @@ class Freeze_test : public beast::unit_test::suite env.close(); } - if (features[featureDeepFreeze]) - { - { - // Is created via a TrustSet with SetDeepFreeze flag - // but not tfSetFreeze - // test: does not set LowDeepFreeze | HighDeepFreeze flags - env(trust(G1, bob["USD"](0), tfSetDeepFreeze)); - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - BEAST_EXPECT(checkArraySize(affected, 1u)); - auto ff = affected[0u][sfModifiedNode.fieldName] - [sfFinalFields.fieldName]; - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowDeepFreeze)); - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); - env.close(); - } - - { - // Is created via a TrustSet with SetDeepFreeze flag - // and tfSetFreeze - // test: sets LowDeepFreeze | HighDeepFreeze flags - env(trust(G1, bob["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) - return; - auto ff = affected[1u][sfModifiedNode.fieldName] - [sfFinalFields.fieldName]; - BEAST_EXPECT( - ff[sfLowLimit.fieldName] == - G1["USD"](0).value().getJson(JsonOptions::none)); - BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowDeepFreeze); - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); - env.close(); - } - } - { // Account with line frozen by issuer // test: can buy more assets on that line env(offer(bob, G1["USD"](5), XRP(25))); auto affected = env.meta()->getJson( JsonOptions::none)[sfAffectedNodes.fieldName]; - BEAST_EXPECT( - checkArraySize(affected, 4u) || checkArraySize(affected, 5u)); + if (!BEAST_EXPECT(checkArraySize(affected, 5u))) + return; auto ff = affected[3u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; - if (affected.size() == 5) - { - BEAST_EXPECT( - ff[sfHighLimit.fieldName] == - bob["USD"](100).value().getJson(JsonOptions::none)); - auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15} - .value() - .getJson(JsonOptions::none); - BEAST_EXPECT(ff[sfBalance.fieldName] == amt); - } - else if (affected.size() == 4) - { - BEAST_EXPECT(ff[sfHighLimit.fieldName] == Json::nullValue); - } + BEAST_EXPECT( + ff[sfHighLimit.fieldName] == + bob["USD"](100).value().getJson(JsonOptions::none)); + auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15} + .value() + .getJson(JsonOptions::none); + BEAST_EXPECT(ff[sfBalance.fieldName] == amt); env.close(); } @@ -167,11 +123,8 @@ class Freeze_test : public beast::unit_test::suite // test: can not sell assets from that line env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER)); - // test: can receive Payment on that line unless deep frozen - if (features[featureDeepFreeze]) - env(pay(alice, bob, G1["USD"](1)), ter(tecPATH_DRY)); - else - env(pay(alice, bob, G1["USD"](1))); + // test: can receive Payment on that line + env(pay(alice, bob, G1["USD"](1))); // test: can not make Payment from that line env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY)); @@ -193,12 +146,7 @@ class Freeze_test : public beast::unit_test::suite if (!BEAST_EXPECT(bobLine)) return; BEAST_EXPECT(bobLine[jss::freeze] == true); - if (features[featureDeepFreeze] && features[featureFlowCross]) - BEAST_EXPECT(bobLine[jss::balance] == "-10"); - else if (features[featureFlowCross]) - BEAST_EXPECT(bobLine[jss::balance] == "-16"); - else if (features[featureDeepFreeze]) - BEAST_EXPECT(bobLine[jss::balance] == "-15"); + BEAST_EXPECT(bobLine[jss::balance] == "-16"); } { @@ -216,13 +164,9 @@ class Freeze_test : public beast::unit_test::suite if (!BEAST_EXPECT(g1Line)) return; BEAST_EXPECT(g1Line[jss::freeze_peer] == true); - if (features[featureDeepFreeze] && features[featureFlowCross]) - BEAST_EXPECT(g1Line[jss::balance] == "10"); - else if (features[featureFlowCross]) - BEAST_EXPECT(g1Line[jss::balance] == "16"); - else if (features[featureDeepFreeze]) - BEAST_EXPECT(g1Line[jss::balance] == "15"); + BEAST_EXPECT(g1Line[jss::balance] == "16"); } + { // Is cleared via a TrustSet with ClearFreeze flag // test: sets LowFreeze | HighFreeze flags @@ -242,6 +186,161 @@ class Freeze_test : public beast::unit_test::suite } } + void + testDeepFreeze(FeatureBitset features) + { + testcase("Deep Freeze"); + + using namespace test::jtx; + Env env(*this, features); + + Account G1{"G1"}; + Account A1{"A1"}; + + env.fund(XRP(10000), G1, A1); + env.close(); + + env.trust(G1["USD"](1000), A1); + env.close(); + + if (features[featureDeepFreeze]) + { + // test: Issuer deep freezing the trust line in a single + // transaction + env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + { + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = affected[1u][sfModifiedNode.fieldName] + [sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[jss::Flags].asUInt() & + (lsfLowFreeze | lsfLowDeepFreeze)); + BEAST_EXPECT( + !(ff[jss::Flags].asUInt() & + (lsfHighFreeze | lsfHighDeepFreeze))); + env.close(); + } + + // test: Issuer clearing deep freeze and normal freeze in a single + // transaction + env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + { + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = affected[1u][sfModifiedNode.fieldName] + [sfFinalFields.fieldName]; + BEAST_EXPECT( + !(ff[jss::Flags].asUInt() & + (lsfLowFreeze | lsfLowDeepFreeze))); + BEAST_EXPECT( + !(ff[jss::Flags].asUInt() & + (lsfHighFreeze | lsfHighDeepFreeze))); + env.close(); + } + + // test: Issuer deep freezing not already frozen line must fail + env(trust(G1, A1["USD"](0), tfSetDeepFreeze), + ter(tecNO_PERMISSION)); + + env(trust(G1, A1["USD"](0), tfSetFreeze)); + env.close(); + + // test: Issuer deep freezing already frozen trust line + env(trust(G1, A1["USD"](0), tfSetDeepFreeze)); + { + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = affected[1u][sfModifiedNode.fieldName] + [sfFinalFields.fieldName]; + BEAST_EXPECT( + ff[jss::Flags].asUInt() & + (lsfLowFreeze | lsfLowDeepFreeze)); + BEAST_EXPECT( + !(ff[jss::Flags].asUInt() & + (lsfHighFreeze | lsfHighDeepFreeze))); + env.close(); + } + + // test: Issuer can't clear normal freeze when line is deep frozen + env(trust(G1, A1["USD"](0), tfClearFreeze), ter(tecNO_PERMISSION)); + + // test: Issuer clearing deep freeze but normal freeze is still in + // effect + env(trust(G1, A1["USD"](0), tfClearDeepFreeze)); + { + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return; + auto ff = affected[1u][sfModifiedNode.fieldName] + [sfFinalFields.fieldName]; + BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze); + BEAST_EXPECT( + !(ff[jss::Flags].asUInt() & + (lsfHighFreeze | lsfHighDeepFreeze))); + env.close(); + } + } + else + { + // test: applying deep freeze before amendment fails + env(trust(G1, A1["USD"](0), tfSetDeepFreeze), ter(temINVALID_FLAG)); + + // test: clearing deep freeze before amendment fails + env(trust(G1, A1["USD"](0), tfClearDeepFreeze), + ter(temINVALID_FLAG)); + } + } + + void + testSetAndClean(FeatureBitset features) + { + testcase("Deep Freeze"); + + using namespace test::jtx; + Env env(*this, features); + + Account G1{"G1"}; + Account A1{"A1"}; + + env.fund(XRP(10000), G1, A1); + env.close(); + + env.trust(G1["USD"](1000), A1); + env.close(); + + if (features[featureDeepFreeze]) + { + // test: can't have both set and clear flag families in the same + // transaction + env(trust(G1, A1["USD"](0), tfSetFreeze | tfClearFreeze), + ter(tecNO_PERMISSION)); + env(trust(G1, A1["USD"](0), tfSetFreeze | tfClearDeepFreeze), + ter(tecNO_PERMISSION)); + env(trust(G1, A1["USD"](0), tfSetDeepFreeze | tfClearFreeze), + ter(tecNO_PERMISSION)); + env(trust(G1, A1["USD"](0), tfSetDeepFreeze | tfClearDeepFreeze), + ter(tecNO_PERMISSION)); + } + else + { + // test: old behavior, transaction succeed with no effect + env(trust(G1, A1["USD"](0), tfSetFreeze | tfClearFreeze)); + { + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + BEAST_EXPECT(checkArraySize(affected, 1u)); + } + } + } + void testGlobalFreeze(FeatureBitset features) { @@ -410,15 +509,32 @@ class Freeze_test : public beast::unit_test::suite Account G1{"G1"}; Account A1{"A1"}; + Account frozenAcc{"A2"}; + Account deepFrozenAcc{"A3"}; env.fund(XRP(12000), G1); env.fund(XRP(1000), A1); + env.fund(XRP(1000), frozenAcc); + env.fund(XRP(1000), deepFrozenAcc); env.close(); env.trust(G1["USD"](1000), A1); + env.trust(G1["USD"](1000), frozenAcc); + env.trust(G1["USD"](1000), deepFrozenAcc); env.close(); env(pay(G1, A1, G1["USD"](1000))); + env(pay(G1, frozenAcc, G1["USD"](1000))); + env(pay(G1, deepFrozenAcc, G1["USD"](1000))); + + // Freezing and deep freezing some of the trust lines to check deep + // freeze and clearing of freeze separately + env(trust(G1, frozenAcc["USD"](0), tfSetFreeze)); + if (features[featureDeepFreeze]) + { + env(trust( + G1, deepFrozenAcc["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + } env.close(); // TrustSet NoFreeze @@ -444,15 +560,34 @@ class Freeze_test : public beast::unit_test::suite env.require(flags(G1, asfGlobalFreeze)); // test: trustlines can't be frozen - env(trust(G1, A1["USD"](0), tfSetFreeze)); - auto affected = - env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 1u))) - return; + if (features[featureDeepFreeze]) + { + env(trust(G1, A1["USD"](0), tfSetFreeze), ter(tecNO_PERMISSION)); + + // test: cannot deep freeze already frozen line + env(trust(G1, frozenAcc["USD"](0), tfSetDeepFreeze), + ter(tecNO_PERMISSION)); + } + else + { + env(trust(G1, A1["USD"](0), tfSetFreeze)); + auto affected = env.meta()->getJson( + JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 1u))) + return; - auto let = - affected[0u][sfModifiedNode.fieldName][sfLedgerEntryType.fieldName]; - BEAST_EXPECT(let == jss::AccountRoot); + auto let = affected[0u][sfModifiedNode.fieldName] + [sfLedgerEntryType.fieldName]; + BEAST_EXPECT(let == jss::AccountRoot); + } + + // test: can clear freeze on account + env(trust(G1, frozenAcc["USD"](0), tfClearFreeze)); + if (features[featureDeepFreeze]) + { + // test: can clear deep freeze on account + env(trust(G1, deepFrozenAcc["USD"](0), tfClearDeepFreeze)); + } } void @@ -568,6 +703,8 @@ class Freeze_test : public beast::unit_test::suite { auto testAll = [this](FeatureBitset features) { testRippleState(features); + testDeepFreeze(features); + testSetAndClean(features); testGlobalFreeze(features); testNoFreeze(features); testOffersWhenFrozen(features); diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 6335bd36e79..684d92c60d3 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -26,6 +26,42 @@ #include #include +namespace { + +uint32_t +applyFreezeFlags( + uint32_t uFlags, + bool bHigh, + bool bNoFreeze, + bool bSetFreeze, + bool bClearFreeze, + bool bSetDeepFreeze, + bool bClearDeepFreeze) +{ + if (bSetFreeze && !bClearFreeze && !bNoFreeze) + { + uFlags |= (bHigh ? ripple::lsfHighFreeze : ripple::lsfLowFreeze); + } + else if (bClearFreeze && !bSetFreeze) + { + uFlags &= ~(bHigh ? ripple::lsfHighFreeze : ripple::lsfLowFreeze); + } + if (bSetDeepFreeze && !bClearDeepFreeze && !bNoFreeze) + { + uFlags |= + (bHigh ? ripple::lsfHighDeepFreeze : ripple::lsfLowDeepFreeze); + } + else if (bClearDeepFreeze && !bSetDeepFreeze) + { + uFlags &= + ~(bHigh ? ripple::lsfHighDeepFreeze : ripple::lsfLowDeepFreeze); + } + + return uFlags; +} + +} // namespace + namespace ripple { NotTEC @@ -192,6 +228,63 @@ SetTrust::preclaim(PreclaimContext const& ctx) } } + // Checking all freeze/deep freeze flag invariants. + auto sleRippleState = + ctx.view.read(keylet::line(id, uDstAccountID, currency)); + + if (sleRippleState) + { + if (ctx.view.rules().enabled(featureDeepFreeze)) + { + bool const bNoFreeze = sle->isFlag(lsfNoFreeze); + bool const bSetFreeze = (uTxFlags & tfSetFreeze); + bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze); + + if (bNoFreeze) + { + if (bSetFreeze || bSetDeepFreeze) + { + // Cannot freeze the trust line if NoFreeze is set + return tecNO_PERMISSION; + } + } + bool const bClearFreeze = (uTxFlags & tfClearFreeze); + bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze); + if ((bSetFreeze || bSetDeepFreeze) && + (bClearFreeze || bClearDeepFreeze)) + { + // Freezing and unfreezing in the same transaction should be + // illegal + return tecNO_PERMISSION; + } + + bool const bHigh = id > uDstAccountID; + // Fetching current state of trust line + std::uint32_t uFlags = sleRippleState->getFieldU32(sfFlags); + // Computing expected trust line state + uFlags = applyFreezeFlags( + uFlags, + bHigh, + bNoFreeze, + bSetFreeze, + bClearFreeze, + bSetDeepFreeze, + bClearDeepFreeze); + + auto const frozen = uFlags & (bHigh ? lsfHighFreeze : lsfLowFreeze); + auto const deepFrozen = + uFlags & (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + + // Trying to set deep freeze on not already frozen trust line must + // fail. This also checks that clearing normal freeze while deep + // frozen must not work + if (deepFrozen && !frozen) + { + return tecNO_PERMISSION; + } + } + } + return tesSUCCESS; } @@ -207,7 +300,7 @@ SetTrust::doApply() Currency const currency(saLimitAmount.getCurrency()); AccountID uDstAccountID(saLimitAmount.getIssuer()); - // true, iff current is high account. + // true, if current is high account. bool const bHigh = account_ > uDstAccountID; auto const sle = view().peek(keylet::account(account_)); @@ -257,10 +350,10 @@ SetTrust::doApply() auto viewJ = ctx_.app.journal("View"); - // Trust lines to self are impossible but because of the old bug there are - // two on 19-02-2022. This code was here to allow those trust lines to be - // deleted. The fixTrustLinesToSelf fix amendment will remove them when it - // enables so this code will no longer be needed. + // Trust lines to self are impossible but because of the old bug there + // are two on 19-02-2022. This code was here to allow those trust lines + // to be deleted. The fixTrustLinesToSelf fix amendment will remove them + // when it enables so this code will no longer be needed. if (!view().rules().enabled(fixTrustLinesToSelf) && account_ == uDstAccountID) { @@ -420,36 +513,17 @@ SetTrust::doApply() uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } - if (bSetFreeze && !bClearFreeze && !sle->isFlag(lsfNoFreeze)) - { - uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); - } - else if (bClearFreeze && !bSetFreeze) - { - uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); - } - - if (bSetDeepFreeze && !bClearDeepFreeze && !sle->isFlag(lsfNoFreeze)) - { - uFlagsOut |= (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); - } - else if (bClearDeepFreeze && !bSetDeepFreeze) - { - uFlagsOut &= ~(bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); - } - - // Checking freeze invariants - auto const frozen = uFlagsOut & (bHigh ? lsfHighFreeze : lsfLowFreeze); - auto const deepFrozen = - uFlagsOut & (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); - - // Trying to set deep freeze on not already frozen trust line must fail - // This also checks that clearing normal freeze while deep frozen must - // not work - if (deepFrozen && !frozen) - { - return tecNO_PERMISSION; - } + // I really want to get rid of NoFreeze check here, but we have to use + // it to maintain previous behavior + bool const bNoFreeze = sle->isFlag(lsfNoFreeze); + uFlagsOut = applyFreezeFlags( + uFlagsOut, + bHigh, + bNoFreeze, + bSetFreeze, + bClearFreeze, + bSetDeepFreeze, + bClearDeepFreeze); if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; @@ -532,8 +606,8 @@ SetTrust::doApply() // Reserve is not scaled by load. else if (bReserveIncrease && mPriorBalance < reserveCreate) { - JLOG(j_.trace()) - << "Delay transaction: Insufficent reserve to add trust line."; + JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " + "add trust line."; // Another transaction could provide XRP to the account and then // this transaction would succeed. @@ -549,17 +623,18 @@ SetTrust::doApply() // Line does not exist. else if ( !saLimitAmount && // Setting default limit. - (!bQualityIn || !uQualityIn) && // Not setting quality in or setting - // default quality in. - (!bQualityOut || !uQualityOut) && // Not setting quality out or setting - // default quality out. + (!bQualityIn || !uQualityIn) && // Not setting quality in or + // setting default quality in. + (!bQualityOut || !uQualityOut) && // Not setting quality out or + // setting default quality out. (!bSetAuth)) { JLOG(j_.trace()) << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } - else if (mPriorBalance < reserveCreate) // Reserve is not scaled by load. + else if (mPriorBalance < reserveCreate) // Reserve is not scaled by + // load. { JLOG(j_.trace()) << "Delay transaction: Line does not exist. " "Insufficent reserve to create line."; From a1626632d173311d04475601c65c33e35b60f62f Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Wed, 20 Nov 2024 17:51:45 +0000 Subject: [PATCH 07/37] Moved modified trustline flags extraction into separate function --- src/test/app/Freeze_test.cpp | 74 +++++++++++++----------------------- 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index eb87f775107..18053e90235 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -209,18 +209,9 @@ class Freeze_test : public beast::unit_test::suite // transaction env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); { - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) - return; - auto ff = affected[1u][sfModifiedNode.fieldName] - [sfFinalFields.fieldName]; - BEAST_EXPECT( - ff[jss::Flags].asUInt() & - (lsfLowFreeze | lsfLowDeepFreeze)); - BEAST_EXPECT( - !(ff[jss::Flags].asUInt() & - (lsfHighFreeze | lsfHighDeepFreeze))); + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); + BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } @@ -228,18 +219,9 @@ class Freeze_test : public beast::unit_test::suite // transaction env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); { - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) - return; - auto ff = affected[1u][sfModifiedNode.fieldName] - [sfFinalFields.fieldName]; - BEAST_EXPECT( - !(ff[jss::Flags].asUInt() & - (lsfLowFreeze | lsfLowDeepFreeze))); - BEAST_EXPECT( - !(ff[jss::Flags].asUInt() & - (lsfHighFreeze | lsfHighDeepFreeze))); + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(!(flags & (lsfLowFreeze | lsfLowDeepFreeze))); + BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } @@ -253,18 +235,9 @@ class Freeze_test : public beast::unit_test::suite // test: Issuer deep freezing already frozen trust line env(trust(G1, A1["USD"](0), tfSetDeepFreeze)); { - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) - return; - auto ff = affected[1u][sfModifiedNode.fieldName] - [sfFinalFields.fieldName]; - BEAST_EXPECT( - ff[jss::Flags].asUInt() & - (lsfLowFreeze | lsfLowDeepFreeze)); - BEAST_EXPECT( - !(ff[jss::Flags].asUInt() & - (lsfHighFreeze | lsfHighDeepFreeze))); + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); + BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } @@ -275,16 +248,9 @@ class Freeze_test : public beast::unit_test::suite // effect env(trust(G1, A1["USD"](0), tfClearDeepFreeze)); { - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) - return; - auto ff = affected[1u][sfModifiedNode.fieldName] - [sfFinalFields.fieldName]; - BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze); - BEAST_EXPECT( - !(ff[jss::Flags].asUInt() & - (lsfHighFreeze | lsfHighDeepFreeze))); + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(flags & lsfLowFreeze); + BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } } @@ -336,7 +302,8 @@ class Freeze_test : public beast::unit_test::suite { auto affected = env.meta()->getJson( JsonOptions::none)[sfAffectedNodes.fieldName]; - BEAST_EXPECT(checkArraySize(affected, 1u)); + BEAST_EXPECT(checkArraySize( + affected, 1u)); // means no trustline changes } } } @@ -697,6 +664,19 @@ class Freeze_test : public beast::unit_test::suite return; } + uint32_t + modifiedTrustlineFlags(test::jtx::Env& env) + { + using namespace test::jtx; + auto const affected = + env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName]; + if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + return 0; + auto const ff = + affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; + return ff[jss::Flags].asUInt(); + } + public: void run() override From c7c26ac202d5463aa2ac0bdfcaedf2604fcd29e7 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Thu, 21 Nov 2024 10:55:45 +0000 Subject: [PATCH 08/37] Added more check to test cases --- src/test/app/Freeze_test.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 18053e90235..9171d577d8b 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -250,7 +250,9 @@ class Freeze_test : public beast::unit_test::suite { auto const flags = modifiedTrustlineFlags(env); BEAST_EXPECT(flags & lsfLowFreeze); - BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); + BEAST_EXPECT( + !(flags & + (lsfLowDeepFreeze | lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } } @@ -297,7 +299,8 @@ class Freeze_test : public beast::unit_test::suite } else { - // test: old behavior, transaction succeed with no effect + // test: old behavior, transaction succeed with no effect on a + // trust line env(trust(G1, A1["USD"](0), tfSetFreeze | tfClearFreeze)); { auto affected = env.meta()->getJson( @@ -497,10 +500,20 @@ class Freeze_test : public beast::unit_test::suite // Freezing and deep freezing some of the trust lines to check deep // freeze and clearing of freeze separately env(trust(G1, frozenAcc["USD"](0), tfSetFreeze)); + { + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(flags & lsfLowFreeze); + BEAST_EXPECT(!(flags & lsfHighFreeze)); + } if (features[featureDeepFreeze]) { env(trust( G1, deepFrozenAcc["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + { + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(!(flags & (lsfLowFreeze | lsfLowDeepFreeze))); + BEAST_EXPECT(flags & (lsfHighFreeze | lsfHighDeepFreeze)); + } } env.close(); @@ -537,6 +550,8 @@ class Freeze_test : public beast::unit_test::suite } else { + // test: previous functionality, checking there's no changes to a + // trust line env(trust(G1, A1["USD"](0), tfSetFreeze)); auto affected = env.meta()->getJson( JsonOptions::none)[sfAffectedNodes.fieldName]; @@ -550,10 +565,20 @@ class Freeze_test : public beast::unit_test::suite // test: can clear freeze on account env(trust(G1, frozenAcc["USD"](0), tfClearFreeze)); + { + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(!(flags & lsfLowFreeze)); + } + if (features[featureDeepFreeze]) { // test: can clear deep freeze on account env(trust(G1, deepFrozenAcc["USD"](0), tfClearDeepFreeze)); + { + auto const flags = modifiedTrustlineFlags(env); + BEAST_EXPECT(flags & lsfHighFreeze); + BEAST_EXPECT(!(flags & lsfHighDeepFreeze)); + } } } From 805102ad82df420e7d32ad2038e3c80c85bd89fd Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 22 Nov 2024 14:19:01 +0000 Subject: [PATCH 09/37] Added payment tests for freeze and deep freeze --- src/test/app/Freeze_test.cpp | 122 ++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 3 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 9171d577d8b..0b3377e4c80 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -268,9 +268,9 @@ class Freeze_test : public beast::unit_test::suite } void - testSetAndClean(FeatureBitset features) + testSetAndClear(FeatureBitset features) { - testcase("Deep Freeze"); + testcase("Freeze Set and Clear"); using namespace test::jtx; Env env(*this, features); @@ -689,6 +689,121 @@ class Freeze_test : public beast::unit_test::suite return; } + void + testPaymentsWhenDeepFrozen(FeatureBitset features) + { + testcase("Direct payments on frozen trust lines"); + + using namespace test::jtx; + Env env(*this, features); + + Account G1{"G1"}; + Account A1{"A1"}; + Account A2{"A2"}; + + env.fund(XRP(10000), G1); + env.fund(XRP(10000), A1); + env.fund(XRP(10000), A2); + env.close(); + + env.trust(G1["USD"](1000), A1); + env.trust(G1["USD"](1000), A2); + env.close(); + + env(pay(G1, A1, G1["USD"](1000))); + env(pay(G1, A2, G1["USD"](1000))); + env.close(); + + // Checking payments before freeze + // To issuer: + env(pay(A1, G1, G1["USD"](1))); + env(pay(A2, G1, G1["USD"](1))); + env.close(); + + // To each other: + env(pay(A1, A2, G1["USD"](1))); + env(pay(A2, A1, G1["USD"](1))); + env.close(); + + // Freeze A1 + env(trust(G1, A1["USD"](0), tfSetFreeze)); + env.close(); + + // Issuer and A1 can send payments to each other + env(pay(A1, G1, G1["USD"](1))); + env(pay(G1, A1, G1["USD"](1))); + env.close(); + + // A1 cannot send tokens to A2 + env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); + + // A2 can still send to A1 + env(pay(A2, A1, G1["USD"](1))); + env.close(); + + if (features[featureDeepFreeze]) + { + // Deep freeze A1 + env(trust(G1, A1["USD"](0), tfSetDeepFreeze)); + env.close(); + + // Issuer and A1 can send payments to each other + env(pay(A1, G1, G1["USD"](1))); + env(pay(G1, A1, G1["USD"](1))); + env.close(); + + // A1 cannot send tokens to A2 + env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); + + // A2 cannot send tokens to A1 + env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + + // Clear deep freeze on A1 + env(trust(G1, A1["USD"](0), tfClearDeepFreeze)); + env.close(); + } + + // Clear freeze on A1 + env(trust(G1, A1["USD"](0), tfClearFreeze)); + env.close(); + + // A1 freezes trust line + env(trust(A1, G1["USD"](0), tfSetFreeze)); + env.close(); + + // Issuer and A1 can send payments to each other + env(pay(A1, G1, G1["USD"](1))); + // env(pay(G1, A1, G1["USD"](1))); // BUG: seems like + env.close(); + + // Issuer and A2 must not be affected + env(pay(A2, G1, G1["USD"](1))); + env(pay(G1, A2, G1["USD"](1))); + env.close(); + + // A1 can send tokens to A2 + env(pay(A1, A2, G1["USD"](1))); + env.close(); + + // A2 cannot send tokens to A1 + env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + + if (features[featureDeepFreeze]) + { + // A1 deep freezes trust line + env(trust(A1, G1["USD"](0), tfSetDeepFreeze)); + // Issuer and A1 can send payments to each other + env(pay(A1, G1, G1["USD"](1))); + // env(pay(G1, A1, G1["USD"](1))); // BUG: seems like + env.close(); + + // A1 cannot send tokens to A2 + env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); + // A2 cannot send tokens to A1 + env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + } + } + uint32_t modifiedTrustlineFlags(test::jtx::Env& env) { @@ -709,10 +824,11 @@ class Freeze_test : public beast::unit_test::suite auto testAll = [this](FeatureBitset features) { testRippleState(features); testDeepFreeze(features); - testSetAndClean(features); + testSetAndClear(features); testGlobalFreeze(features); testNoFreeze(features); testOffersWhenFrozen(features); + testPaymentsWhenDeepFrozen(features); }; using namespace test::jtx; auto const sa = supported_amendments(); From de58a26b794cd14f372d6677823ca4f2b93e70b2 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 22 Nov 2024 17:46:49 +0000 Subject: [PATCH 10/37] Clarified behavior of individual freeze --- src/test/app/Freeze_test.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 0b3377e4c80..83573042410 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -771,20 +771,20 @@ class Freeze_test : public beast::unit_test::suite env(trust(A1, G1["USD"](0), tfSetFreeze)); env.close(); - // Issuer and A1 can send payments to each other - env(pay(A1, G1, G1["USD"](1))); - // env(pay(G1, A1, G1["USD"](1))); // BUG: seems like - env.close(); - // Issuer and A2 must not be affected env(pay(A2, G1, G1["USD"](1))); env(pay(G1, A2, G1["USD"](1))); env.close(); + // A1 can send tokens to the issuer + env(pay(A1, G1, G1["USD"](1))); + env.close(); // A1 can send tokens to A2 env(pay(A1, A2, G1["USD"](1))); env.close(); + // Issuer cannot sent tokens to A1 + env(pay(G1, A1, G1["USD"](1)), ter(tecPATH_DRY)); // A2 cannot send tokens to A1 env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); @@ -792,15 +792,23 @@ class Freeze_test : public beast::unit_test::suite { // A1 deep freezes trust line env(trust(A1, G1["USD"](0), tfSetDeepFreeze)); - // Issuer and A1 can send payments to each other + env.close(); + + // Issuer and A2 must not be affected + env(pay(A2, G1, G1["USD"](1))); + env(pay(G1, A2, G1["USD"](1))); + env.close(); + + // A1 can still send token to issuer env(pay(A1, G1, G1["USD"](1))); - // env(pay(G1, A1, G1["USD"](1))); // BUG: seems like env.close(); - // A1 cannot send tokens to A2 - env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); + // Issuer cannot send tokens to A1 + env(pay(G1, A1, G1["USD"](1)), ter(tecPATH_DRY)); // A2 cannot send tokens to A1 env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + // A1 cannot send tokens to A2 + env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); } } From 91d291a6c339e262c2da982cd80cada7b130dae4 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Thu, 28 Nov 2024 16:46:19 +0000 Subject: [PATCH 11/37] Added offer test for freeze and deep freeze --- src/test/app/Freeze_test.cpp | 111 +++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 5 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 83573042410..fd14c3e2f29 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -689,6 +689,109 @@ class Freeze_test : public beast::unit_test::suite return; } + void + testOffersWhenDeepFrozen(FeatureBitset features) + { + testcase("Offers on frozen trust lines"); + + using namespace test::jtx; + Env env(*this, features); + + Account G1{"G1"}; + Account A1{"A1"}; + Account A2{"A2"}; + Account A3{"A3"}; + + env.fund(XRP(10000), G1, A1, A2, A3); + env.close(); + + env.trust(G1["USD"](1000), A1, A2, A3); + env.close(); + + env(pay(G1, A1, G1["USD"](1000))); + env(pay(G1, A2, G1["USD"](1000))); + env.close(); + + // Making large passive sell offer + // Wants to sell 90 USD for 100 XRP + env(offer(A2, XRP(100), G1["USD"](90)), txflags(tfPassive)); + env.close(); + // Making large passive buy offer + // Wants to buy 100 USD for 100 XRP + env(offer(A3, G1["USD"](100), XRP(100)), txflags(tfPassive)); + env.close(); + env.require(offers(A2, 1), offers(A3, 1)); + + // Checking A1 can buy from A2 by crossing it's offer + env(offer(A1, G1["USD"](1), XRP(2))); + env.close(); + env.require(balance(A1, G1["USD"](1001)), balance(A2, G1["USD"](999))); + + // Checking A1 can sell to A3 by crossing it's offer + env(offer(A1, XRP(1), G1["USD"](1))); + env.close(); + env.require(balance(A1, G1["USD"](1000)), balance(A3, G1["USD"](1))); + + // Testing aggressive and passive offer placing A1 frozen by issuer + { + env(trust(G1, A1["USD"](0), tfSetFreeze)); + env.close(); + + // test: can still make passive buy offer + env(offer(A1, G1["USD"](1), XRP(0.5)), txflags(tfPassive)); + env.close(); + env.require(balance(A1, G1["USD"](1000)), offers(A1, 1)); + // Cleanup + env(offer_cancel(A1, env.seq(A1) - 1)); + env.close(); + + // test: can still buy from A2 + env(offer(A1, G1["USD"](1), XRP(2))); + env.close(); + env.require( + balance(A1, G1["USD"](1001)), + balance(A2, G1["USD"](998)), + offers(A1, 0)); + + // test: cannot create passive sell offer + env(offer(A1, XRP(2), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); + + // test: cannot sell to A3 + env(offer(A1, XRP(1), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); + + env.require( + balance(A1, G1["USD"](1001)), balance(A3, G1["USD"](1))); + env(trust(G1, A1["USD"](0), tfClearFreeze)); + env.close(); + env.require( + balance(A1, G1["USD"](1000)), balance(A3, G1["USD"](2))); + } + + // Testing aggressive and passive offer placing A1 deep frozen by issuer + if (features[featureDeepFreeze]) + { + env(trust(G1, A1["USD"](1000), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: cannot create passive buy offer + env(offer(A1, G1["USD"](1), XRP(0.5)), ter(tecFROZEN)); + + // test: cannot buy from A2 + env(offer(A1, G1["USD"](1), XRP(2)), ter(tecFROZEN)); + + // test: cannot create passive sell offer + env(offer(A1, XRP(2), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + + // test: cannot sell to A3 + env(offer(A1, XRP(1), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + + env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + } + void testPaymentsWhenDeepFrozen(FeatureBitset features) { @@ -701,13 +804,10 @@ class Freeze_test : public beast::unit_test::suite Account A1{"A1"}; Account A2{"A2"}; - env.fund(XRP(10000), G1); - env.fund(XRP(10000), A1); - env.fund(XRP(10000), A2); + env.fund(XRP(10000), G1, A1, A2); env.close(); - env.trust(G1["USD"](1000), A1); - env.trust(G1["USD"](1000), A2); + env.trust(G1["USD"](1000), A1, A2); env.close(); env(pay(G1, A1, G1["USD"](1000))); @@ -836,6 +936,7 @@ class Freeze_test : public beast::unit_test::suite testGlobalFreeze(features); testNoFreeze(features); testOffersWhenFrozen(features); + testOffersWhenDeepFrozen(features); testPaymentsWhenDeepFrozen(features); }; using namespace test::jtx; From e0d59efea4a071868072b46203c648317fa4a112 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 2 Dec 2024 17:19:39 +0000 Subject: [PATCH 12/37] More offer tests --- src/test/app/Freeze_test.cpp | 99 +++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index fd14c3e2f29..0024b7767fb 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -713,8 +713,8 @@ class Freeze_test : public beast::unit_test::suite env.close(); // Making large passive sell offer - // Wants to sell 90 USD for 100 XRP - env(offer(A2, XRP(100), G1["USD"](90)), txflags(tfPassive)); + // Wants to sell 50 USD for 100 XRP + env(offer(A2, XRP(100), G1["USD"](50)), txflags(tfPassive)); env.close(); // Making large passive buy offer // Wants to buy 100 USD for 100 XRP @@ -743,6 +743,7 @@ class Freeze_test : public beast::unit_test::suite env.require(balance(A1, G1["USD"](1000)), offers(A1, 1)); // Cleanup env(offer_cancel(A1, env.seq(A1) - 1)); + env.require(offers(A1, 0)); env.close(); // test: can still buy from A2 @@ -755,37 +756,121 @@ class Freeze_test : public beast::unit_test::suite // test: cannot create passive sell offer env(offer(A1, XRP(2), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env.close(); env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); // test: cannot sell to A3 env(offer(A1, XRP(1), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env.close(); env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); - env.require( - balance(A1, G1["USD"](1001)), balance(A3, G1["USD"](1))); env(trust(G1, A1["USD"](0), tfClearFreeze)); env.close(); - env.require( - balance(A1, G1["USD"](1000)), balance(A3, G1["USD"](2))); } // Testing aggressive and passive offer placing A1 deep frozen by issuer if (features[featureDeepFreeze]) { - env(trust(G1, A1["USD"](1000), tfSetFreeze | tfSetDeepFreeze)); + env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); env.close(); // test: cannot create passive buy offer env(offer(A1, G1["USD"](1), XRP(0.5)), ter(tecFROZEN)); + env.close(); // test: cannot buy from A2 env(offer(A1, G1["USD"](1), XRP(2)), ter(tecFROZEN)); + env.close(); // test: cannot create passive sell offer env(offer(A1, XRP(2), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env.close(); // test: cannot sell to A3 env(offer(A1, XRP(1), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env.close(); + + env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); + } + + // Testing already existing offers behavior after trustline is frozen + { + env.require(balance(A1, G1["USD"](1001))); + env(offer(A1, XRP(1.9), G1["USD"](1))); + env(offer(A1, G1["USD"](1), XRP(1.1))); + env.close(); + env.require(balance(A1, G1["USD"](1001)), offers(A1, 2)); + + env(trust(G1, A1["USD"](0), tfSetFreeze)); + env.close(); + + // A2 wants to sell to A1, must succeed + env.require( + balance(A1, G1["USD"](1001)), balance(A2, G1["USD"](998))); + env(offer(A2, XRP(1.1), G1["USD"](1)), txflags(tfFillOrKill)); + env.close(); + env.require( + balance(A1, G1["USD"](1002)), + balance(A2, G1["USD"](997)), + offers(A1, 1)); + + // A3 wants to buy from A1, must fail + env.require( + balance(A1, G1["USD"](1002)), + balance(A3, G1["USD"](1)), + offers(A1, 1)); + env(offer(A3, G1["USD"](1), XRP(1.9)), + txflags(tfFillOrKill), + ter(tecKILLED)); + env.close(); + env.require( + balance(A1, G1["USD"](1002)), + balance(A3, G1["USD"](1)), + offers(A1, 0)); + + env(trust(G1, A1["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing existing offers behavior after trustline is deep frozen + if (features[featureDeepFreeze]) + { + env.require(balance(A1, G1["USD"](1002))); + env(offer(A1, XRP(1.9), G1["USD"](1))); + env(offer(A1, G1["USD"](1), XRP(1.1))); + env.close(); + env.require(balance(A1, G1["USD"](1002)), offers(A1, 2)); + + env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // A2 wants to sell to A1, must fail + env.require( + balance(A1, G1["USD"](1002)), balance(A2, G1["USD"](997))); + env(offer(A2, XRP(1.1), G1["USD"](1)), + txflags(tfFillOrKill), + ter(tecKILLED)); + env.close(); + env.require( + balance(A1, G1["USD"](1002)), + balance(A2, G1["USD"](997)), + offers(A1, 1)); + + // A3 wants to buy from A1, must fail + env.require( + balance(A1, G1["USD"](1002)), + balance(A3, G1["USD"](1)), + offers(A1, 1)); + env(offer(A3, G1["USD"](1), XRP(1.9)), + txflags(tfFillOrKill), + ter(tecKILLED)); + env.close(); + env.require( + balance(A1, G1["USD"](1002)), + balance(A3, G1["USD"](1)), + offers(A1, 0)); env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); env.close(); From ffa6ccd8b34315c4d1673838f42294db2c9c8f7f Mon Sep 17 00:00:00 2001 From: Vlad <129996061+vvysokikh1@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:21:21 +0000 Subject: [PATCH 13/37] Fixed typo Co-authored-by: Ed Hennis --- src/xrpld/app/paths/detail/StepChecks.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xrpld/app/paths/detail/StepChecks.h b/src/xrpld/app/paths/detail/StepChecks.h index d9ce2cc9d34..07423862eda 100644 --- a/src/xrpld/app/paths/detail/StepChecks.h +++ b/src/xrpld/app/paths/detail/StepChecks.h @@ -54,7 +54,7 @@ checkFreeze( } if (view.rules().enabled(featureDeepFreeze)) { - // Unline normal freeze, a deep frozen trust line acts the same + // Unlike normal freeze, a deep frozen trust line acts the same // regardless of which side froze it if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)) { From f9107a7138aea5d3fd8ef85016f9a51f245acafd Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Wed, 4 Dec 2024 10:39:59 +0000 Subject: [PATCH 14/37] Fixed tx flags check in case trustline doesn't exist yet --- src/xrpld/app/tx/detail/SetTrust.cpp | 91 ++++++++++++++-------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 684d92c60d3..17a0558ce96 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -229,59 +229,56 @@ SetTrust::preclaim(PreclaimContext const& ctx) } // Checking all freeze/deep freeze flag invariants. - auto sleRippleState = - ctx.view.read(keylet::line(id, uDstAccountID, currency)); - - if (sleRippleState) + if (ctx.view.rules().enabled(featureDeepFreeze)) { - if (ctx.view.rules().enabled(featureDeepFreeze)) - { - bool const bNoFreeze = sle->isFlag(lsfNoFreeze); - bool const bSetFreeze = (uTxFlags & tfSetFreeze); - bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze); + bool const bNoFreeze = sle->isFlag(lsfNoFreeze); + bool const bSetFreeze = (uTxFlags & tfSetFreeze); + bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze); - if (bNoFreeze) - { - if (bSetFreeze || bSetDeepFreeze) - { - // Cannot freeze the trust line if NoFreeze is set - return tecNO_PERMISSION; - } - } - bool const bClearFreeze = (uTxFlags & tfClearFreeze); - bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze); - if ((bSetFreeze || bSetDeepFreeze) && - (bClearFreeze || bClearDeepFreeze)) + if (bNoFreeze) + { + if (bSetFreeze || bSetDeepFreeze) { - // Freezing and unfreezing in the same transaction should be - // illegal + // Cannot freeze the trust line if NoFreeze is set return tecNO_PERMISSION; } + } + bool const bClearFreeze = (uTxFlags & tfClearFreeze); + bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze); + if ((bSetFreeze || bSetDeepFreeze) && + (bClearFreeze || bClearDeepFreeze)) + { + // Freezing and unfreezing in the same transaction should be + // illegal + return tecNO_PERMISSION; + } - bool const bHigh = id > uDstAccountID; - // Fetching current state of trust line - std::uint32_t uFlags = sleRippleState->getFieldU32(sfFlags); - // Computing expected trust line state - uFlags = applyFreezeFlags( - uFlags, - bHigh, - bNoFreeze, - bSetFreeze, - bClearFreeze, - bSetDeepFreeze, - bClearDeepFreeze); - - auto const frozen = uFlags & (bHigh ? lsfHighFreeze : lsfLowFreeze); - auto const deepFrozen = - uFlags & (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); - - // Trying to set deep freeze on not already frozen trust line must - // fail. This also checks that clearing normal freeze while deep - // frozen must not work - if (deepFrozen && !frozen) - { - return tecNO_PERMISSION; - } + bool const bHigh = id > uDstAccountID; + // Fetching current state of trust line + auto const sleRippleState = + ctx.view.read(keylet::line(id, uDstAccountID, currency)); + std::uint32_t uFlags = + sleRippleState ? sleRippleState->getFieldU32(sfFlags) : 0u; + // Computing expected trust line state + uFlags = applyFreezeFlags( + uFlags, + bHigh, + bNoFreeze, + bSetFreeze, + bClearFreeze, + bSetDeepFreeze, + bClearDeepFreeze); + + auto const frozen = uFlags & (bHigh ? lsfHighFreeze : lsfLowFreeze); + auto const deepFrozen = + uFlags & (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); + + // Trying to set deep freeze on not already frozen trust line must + // fail. This also checks that clearing normal freeze while deep + // frozen must not work + if (deepFrozen && !frozen) + { + return tecNO_PERMISSION; } } From 1f9ecb8bfeb1302eca2ea5fba16f8a221c2e40b5 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Wed, 4 Dec 2024 16:12:47 +0000 Subject: [PATCH 15/37] Added tests for trustline creation freeze handling --- src/test/app/Freeze_test.cpp | 88 ++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 0024b7767fb..332f7bbf2e3 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -209,7 +209,7 @@ class Freeze_test : public beast::unit_test::suite // transaction env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); @@ -219,7 +219,7 @@ class Freeze_test : public beast::unit_test::suite // transaction env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(!(flags & (lsfLowFreeze | lsfLowDeepFreeze))); BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); @@ -235,7 +235,7 @@ class Freeze_test : public beast::unit_test::suite // test: Issuer deep freezing already frozen trust line env(trust(G1, A1["USD"](0), tfSetDeepFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); @@ -248,7 +248,7 @@ class Freeze_test : public beast::unit_test::suite // effect env(trust(G1, A1["USD"](0), tfClearDeepFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(flags & lsfLowFreeze); BEAST_EXPECT( !(flags & @@ -267,6 +267,55 @@ class Freeze_test : public beast::unit_test::suite } } + void + testCreateFrozenTrustline(FeatureBitset features) + { + testcase("Create Frozen Trustline"); + + using namespace test::jtx; + Env env(*this, features); + + Account G1{"G1"}; + Account A1{"A1"}; + + env.fund(XRP(10000), G1, A1); + env.close(); + + // // test: can create frozen trustline + // { + // env(trust(G1, A1["USD"](1000), tfSetFreeze)); + // auto const flags = getTrustlineFlags(env, 5u, 3u, false); + // BEAST_EXPECT(flags & lsfLowFreeze); + // env.close(); + // env.require(lines(A1, 1)); + // } + + // // Cleanup + // env(trust(G1, A1["USD"](0), tfClearFreeze)); + // env.close(); + // env.require(lines(G1, 0)); + // env.require(lines(A1, 0)); + + // // test: cannot create deep frozen trustline without normal freeze + // if (features[featureDeepFreeze]) + // { + // env(trust(G1, A1["USD"](1000), tfSetDeepFreeze), + // ter(tecNO_PERMISSION)); + // env.close(); + // env.require(lines(A1, 0)); + // } + + // test: can create deep frozen trustline together with normal freeze + if (features[featureDeepFreeze]) + { + env(trust(G1, A1["USD"](1000), tfSetFreeze | tfSetDeepFreeze)); + auto const flags = getTrustlineFlags(env, 5u, 3u, false); + BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); + env.close(); + env.require(lines(A1, 1)); + } + } + void testSetAndClear(FeatureBitset features) { @@ -501,7 +550,7 @@ class Freeze_test : public beast::unit_test::suite // freeze and clearing of freeze separately env(trust(G1, frozenAcc["USD"](0), tfSetFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(flags & lsfLowFreeze); BEAST_EXPECT(!(flags & lsfHighFreeze)); } @@ -510,7 +559,7 @@ class Freeze_test : public beast::unit_test::suite env(trust( G1, deepFrozenAcc["USD"](0), tfSetFreeze | tfSetDeepFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(!(flags & (lsfLowFreeze | lsfLowDeepFreeze))); BEAST_EXPECT(flags & (lsfHighFreeze | lsfHighDeepFreeze)); } @@ -566,7 +615,7 @@ class Freeze_test : public beast::unit_test::suite // test: can clear freeze on account env(trust(G1, frozenAcc["USD"](0), tfClearFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(!(flags & lsfLowFreeze)); } @@ -575,7 +624,7 @@ class Freeze_test : public beast::unit_test::suite // test: can clear deep freeze on account env(trust(G1, deepFrozenAcc["USD"](0), tfClearDeepFreeze)); { - auto const flags = modifiedTrustlineFlags(env); + auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(flags & lsfHighFreeze); BEAST_EXPECT(!(flags & lsfHighDeepFreeze)); } @@ -998,16 +1047,28 @@ class Freeze_test : public beast::unit_test::suite } uint32_t - modifiedTrustlineFlags(test::jtx::Env& env) + getTrustlineFlags( + test::jtx::Env& env, + size_t expectedArraySize, + size_t expectedArrayIndex, + bool modified = true) { using namespace test::jtx; auto const affected = env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) + if (!BEAST_EXPECT(checkArraySize(affected, expectedArraySize))) return 0; - auto const ff = - affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; - return ff[jss::Flags].asUInt(); + + if (modified) + { + return affected[expectedArrayIndex][sfModifiedNode.fieldName] + [sfFinalFields.fieldName][jss::Flags] + .asUInt(); + } + + return affected[expectedArrayIndex][sfCreatedNode.fieldName] + [sfNewFields.fieldName][jss::Flags] + .asUInt(); } public: @@ -1017,6 +1078,7 @@ class Freeze_test : public beast::unit_test::suite auto testAll = [this](FeatureBitset features) { testRippleState(features); testDeepFreeze(features); + testCreateFrozenTrustline(features); testSetAndClear(features); testGlobalFreeze(features); testNoFreeze(features); From 010f36ddfcfe060051cc435a9c3eb8f49b0705df Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 6 Dec 2024 15:10:24 +0000 Subject: [PATCH 16/37] Partially fixed comments --- include/xrpl/protocol/LedgerFormats.h | 12 +- include/xrpl/protocol/detail/features.macro | 7 +- src/test/app/AMMExtended_test.cpp | 180 -------------------- src/test/app/AMM_test.cpp | 20 --- src/xrpld/app/tx/detail/SetTrust.cpp | 9 +- src/xrpld/ledger/detail/View.cpp | 2 +- 6 files changed, 16 insertions(+), 214 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 7359fc192df..0657ce8cf76 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -160,12 +160,12 @@ enum LedgerSpecificFlags { lsfHighAuth = 0x00080000, lsfLowNoRipple = 0x00100000, lsfHighNoRipple = 0x00200000, - lsfLowFreeze = 0x00400000, // True, low side has set freeze flag - lsfHighFreeze = 0x00800000, // True, high side has set freeze flag - lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client - // apps to identify payments via AMM. - lsfLowDeepFreeze = 0x02000000, - lsfHighDeepFreeze = 0x04000000, + lsfLowFreeze = 0x00400000, // True, low side has set freeze flag + lsfHighFreeze = 0x00800000, // True, high side has set freeze flag + lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client + // apps to identify payments via AMM. + lsfLowDeepFreeze = 0x02000000, // True, low side has set deep freeze flag + lsfHighDeepFreeze = 0x04000000, // True, low side has set deep freeze flag // ltSIGNER_LIST lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 6b6c1a742fd..0da0db2161a 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -28,11 +28,12 @@ // Keep it sorted in reverse chronological order. // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +// clang-format off -// InvariantsV1_1 will be changes to Supported::yes when all the -// invariants expected to be included under it are complete. XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo) +// InvariantsV1_1 will be changes to Supported::yes when all the +// invariants expected to be included under it are complete. XRPL_FEATURE(InvariantsV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (NFTokenPageLinks, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (InnerObjTemplate2, Supported::yes, VoteBehavior::DefaultNo) @@ -112,3 +113,5 @@ XRPL_FIX (NFTokenNegOffer, Supported::yes, VoteBehavior::Obsolete) XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete) + +// clang-format on \ No newline at end of file diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 770a673aa2e..0c04bec8fea 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -3615,184 +3615,6 @@ struct AMMExtended_test : public jtx::AMMTest } } - void - testRippleDeepState(FeatureBitset features) - { - testcase("RippleState Deep Freeze"); - - using namespace test::jtx; - Env env(*this, features); - - Account const G1{"G1"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - - env.fund(XRP(1'000), G1, alice, bob); - env.close(); - - env.trust(G1["USD"](100), bob); - env.trust(G1["USD"](205), alice); - env.close(); - - env(pay(G1, bob, G1["USD"](10))); - env(pay(G1, alice, G1["USD"](205))); - env.close(); - - AMM ammAlice(env, alice, XRP(500), G1["USD"](105)); - - { - auto lines = getAccountLines(env, bob); - if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u))) - return; - BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human()); - BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100"); - BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10"); - } - - { - auto lines = getAccountLines(env, alice, G1["USD"]); - if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u))) - return; - BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human()); - BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205"); - // 105 transferred to AMM - BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100"); - } - - { - // Account with line unfrozen (proving operations normally work) - // test: can make Payment on that line - env(pay(alice, bob, G1["USD"](1))); - - // test: can receive Payment on that line - env(pay(bob, alice, G1["USD"](1))); - env.close(); - } - - { - // Is created via a TrustSet with SetFreeze flag - // test: sets LowFreeze | HighFreeze flags - env(trust(G1, bob["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) - return; - auto ff = - affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; - BEAST_EXPECT( - ff[sfLowLimit.fieldName] == - G1["USD"](0).value().getJson(JsonOptions::none)); - if (features[featureDeepFreeze]) - { - BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowDeepFreeze); - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); - } - else - { - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowDeepFreeze)); - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); - } - env.close(); - } - - { - // Account with line deep frozen by issuer - // test: can not buy more assets on that line - if (features[featureDeepFreeze]) - { - env(offer(bob, G1["USD"](5), XRP(25)), ter(tecFROZEN)); - } - else - { - env(offer(bob, G1["USD"](5), XRP(25))); - } - env.close(); - } - - { - // test: can not sell assets from that line - env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER)); - - // test: can not make Payment from that line - env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY)); - - if (features[featureDeepFreeze]) - { - // test: can not receive Payment on that line - env(pay(alice, bob, G1["USD"](1)), ter(tecPATH_DRY)); - } - else - { - // test: can receive Payment on that line - env(pay(alice, bob, G1["USD"](1))); - } - } - - { - // check G1 account lines - // test: shows deep freeze - auto lines = getAccountLines(env, G1); - Json::Value bobLine; - for (auto const& it : lines[jss::lines]) - { - if (it[jss::account] == bob.human()) - { - bobLine = it; - break; - } - } - if (!BEAST_EXPECT(bobLine)) - return; - BEAST_EXPECT(bobLine[jss::freeze] == true); - if (features[featureDeepFreeze]) - BEAST_EXPECT(bobLine[jss::balance] == "-10"); - else - BEAST_EXPECT(bobLine[jss::balance] == "-16"); - } - - { - // test: shows deep freeze peer - auto lines = getAccountLines(env, bob); - Json::Value g1Line; - for (auto const& it : lines[jss::lines]) - { - if (it[jss::account] == G1.human()) - { - g1Line = it; - break; - } - } - if (!BEAST_EXPECT(g1Line)) - return; - BEAST_EXPECT(g1Line[jss::freeze_peer] == true); - if (features[featureDeepFreeze]) - BEAST_EXPECT(g1Line[jss::balance] == "10"); - else - BEAST_EXPECT(g1Line[jss::balance] == "16"); - } - - { - // Is cleared via a TrustSet with ClearDeepFreeze flag - // test: sets LowDeepFreeze | HighDeepFreeze flags - env(trust(G1, bob["USD"](0), tfClearDeepFreeze)); - auto affected = env.meta()->getJson( - JsonOptions::none)[sfAffectedNodes.fieldName]; - if (!features[featureDeepFreeze] && - BEAST_EXPECT(checkArraySize(affected, 1u))) - return; - if (!BEAST_EXPECT(checkArraySize(affected, 2u))) - return; - auto ff = - affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]; - BEAST_EXPECT( - ff[sfLowLimit.fieldName] == - G1["USD"](0).value().getJson(JsonOptions::none)); - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowDeepFreeze)); - BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighDeepFreeze)); - env.close(); - } - } - void testGlobalFreeze(FeatureBitset features) { @@ -4308,12 +4130,10 @@ struct AMMExtended_test : public jtx::AMMTest using namespace test::jtx; auto const sa = supported_amendments(); testRippleState(sa - featureDeepFreeze); - testRippleDeepState(sa - featureDeepFreeze); testGlobalFreeze(sa - featureDeepFreeze); testOffersWhenFrozen(sa - featureDeepFreeze); testRippleState(sa); - testRippleDeepState(sa); testGlobalFreeze(sa); testOffersWhenFrozen(sa); } diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 0320a0cbf74..24b6ff668ec 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -4419,26 +4419,6 @@ struct AMM_test : public jtx::AMMTest std::nullopt, {features}); } - else - { - testAMM( - [&](AMM& ammAlice, Env& env) { - env(trust( - gw, carol["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - env(trust( - gw, alice["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - env.close(); - env(pay(alice, carol, USD(1)), - path(~USD), - sendmax(XRP(10)), - txflags(tfNoRippleDirect | tfPartialPayment), - ter(tesSUCCESS)); - }, - std::nullopt, - 0, - std::nullopt, - {features}); - } } void diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 17a0558ce96..0332b2b6c00 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -29,7 +29,7 @@ namespace { uint32_t -applyFreezeFlags( +computeFreezeFlags( uint32_t uFlags, bool bHigh, bool bNoFreeze, @@ -260,7 +260,7 @@ SetTrust::preclaim(PreclaimContext const& ctx) std::uint32_t uFlags = sleRippleState ? sleRippleState->getFieldU32(sfFlags) : 0u; // Computing expected trust line state - uFlags = applyFreezeFlags( + uFlags = computeFreezeFlags( uFlags, bHigh, bNoFreeze, @@ -510,10 +510,9 @@ SetTrust::doApply() uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } - // I really want to get rid of NoFreeze check here, but we have to use - // it to maintain previous behavior + // Have to use lsfNoFreeze to maintain pre-deep freeze behavior bool const bNoFreeze = sle->isFlag(lsfNoFreeze); - uFlagsOut = applyFreezeFlags( + uFlagsOut = computeFreezeFlags( uFlagsOut, bHigh, bNoFreeze, diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index caeacc14574..a8709d6a3b7 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -265,7 +265,7 @@ isFrozen( isIndividualFrozen(view, account, mptIssue); } -bool +static bool isDeepFrozen( ReadView const& view, AccountID const& account, From 6bae95d9280569042c34d9a8920d83f15138a581 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Tue, 10 Dec 2024 13:11:59 +0000 Subject: [PATCH 17/37] Partially fixed comments --- src/test/app/Freeze_test.cpp | 68 +++++++++++++------------ src/xrpld/app/paths/detail/StepChecks.h | 11 ++-- src/xrpld/app/tx/detail/CashCheck.cpp | 1 + src/xrpld/app/tx/detail/CreateOffer.cpp | 41 +++++++-------- src/xrpld/app/tx/detail/SetTrust.cpp | 3 +- src/xrpld/ledger/View.h | 1 + src/xrpld/ledger/detail/View.cpp | 14 ++--- 7 files changed, 70 insertions(+), 69 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 332f7bbf2e3..a9b73795755 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -210,7 +210,8 @@ class Freeze_test : public beast::unit_test::suite env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); { auto const flags = getTrustlineFlags(env, 2u, 1u); - BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); + BEAST_EXPECT(flags & lsfLowFreeze); + BEAST_EXPECT(flags & lsfLowDeepFreeze); BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } @@ -236,7 +237,8 @@ class Freeze_test : public beast::unit_test::suite env(trust(G1, A1["USD"](0), tfSetDeepFreeze)); { auto const flags = getTrustlineFlags(env, 2u, 1u); - BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); + BEAST_EXPECT(flags & lsfLowFreeze); + BEAST_EXPECT(flags & lsfLowDeepFreeze); BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } @@ -250,20 +252,18 @@ class Freeze_test : public beast::unit_test::suite { auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(flags & lsfLowFreeze); - BEAST_EXPECT( - !(flags & - (lsfLowDeepFreeze | lsfHighFreeze | lsfHighDeepFreeze))); + BEAST_EXPECT(!(flags & lsfLowDeepFreeze)); + BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); env.close(); } } else { // test: applying deep freeze before amendment fails - env(trust(G1, A1["USD"](0), tfSetDeepFreeze), ter(temINVALID_FLAG)); + env(trust(G1, A1["USD"](0), tfSetDeepFreeze), ter(temDISABLED)); // test: clearing deep freeze before amendment fails - env(trust(G1, A1["USD"](0), tfClearDeepFreeze), - ter(temINVALID_FLAG)); + env(trust(G1, A1["USD"](0), tfClearDeepFreeze), ter(temDISABLED)); } } @@ -281,36 +281,37 @@ class Freeze_test : public beast::unit_test::suite env.fund(XRP(10000), G1, A1); env.close(); - // // test: can create frozen trustline - // { - // env(trust(G1, A1["USD"](1000), tfSetFreeze)); - // auto const flags = getTrustlineFlags(env, 5u, 3u, false); - // BEAST_EXPECT(flags & lsfLowFreeze); - // env.close(); - // env.require(lines(A1, 1)); - // } - - // // Cleanup - // env(trust(G1, A1["USD"](0), tfClearFreeze)); - // env.close(); - // env.require(lines(G1, 0)); - // env.require(lines(A1, 0)); - - // // test: cannot create deep frozen trustline without normal freeze - // if (features[featureDeepFreeze]) - // { - // env(trust(G1, A1["USD"](1000), tfSetDeepFreeze), - // ter(tecNO_PERMISSION)); - // env.close(); - // env.require(lines(A1, 0)); - // } + // test: can create frozen trustline + { + env(trust(G1, A1["USD"](1000), tfSetFreeze)); + auto const flags = getTrustlineFlags(env, 5u, 3u, false); + BEAST_EXPECT(flags & lsfLowFreeze); + env.close(); + env.require(lines(A1, 1)); + } + + // Cleanup + env(trust(G1, A1["USD"](0), tfClearFreeze)); + env.close(); + env.require(lines(G1, 0)); + env.require(lines(A1, 0)); + + // test: cannot create deep frozen trustline without normal freeze + if (features[featureDeepFreeze]) + { + env(trust(G1, A1["USD"](1000), tfSetDeepFreeze), + ter(tecNO_PERMISSION)); + env.close(); + env.require(lines(A1, 0)); + } // test: can create deep frozen trustline together with normal freeze if (features[featureDeepFreeze]) { env(trust(G1, A1["USD"](1000), tfSetFreeze | tfSetDeepFreeze)); auto const flags = getTrustlineFlags(env, 5u, 3u, false); - BEAST_EXPECT(flags & (lsfLowFreeze | lsfLowDeepFreeze)); + BEAST_EXPECT(flags & lsfLowFreeze); + BEAST_EXPECT(flags & lsfLowDeepFreeze); env.close(); env.require(lines(A1, 1)); } @@ -561,7 +562,8 @@ class Freeze_test : public beast::unit_test::suite { auto const flags = getTrustlineFlags(env, 2u, 1u); BEAST_EXPECT(!(flags & (lsfLowFreeze | lsfLowDeepFreeze))); - BEAST_EXPECT(flags & (lsfHighFreeze | lsfHighDeepFreeze)); + BEAST_EXPECT(flags & lsfHighFreeze); + BEAST_EXPECT(flags & lsfHighDeepFreeze); } } env.close(); diff --git a/src/xrpld/app/paths/detail/StepChecks.h b/src/xrpld/app/paths/detail/StepChecks.h index 07423862eda..4e8db6c9d08 100644 --- a/src/xrpld/app/paths/detail/StepChecks.h +++ b/src/xrpld/app/paths/detail/StepChecks.h @@ -52,14 +52,11 @@ checkFreeze( { return terNO_LINE; } - if (view.rules().enabled(featureDeepFreeze)) + // Unlike normal freeze, a deep frozen trust line acts the same + // regardless of which side froze it + if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)) { - // Unlike normal freeze, a deep frozen trust line acts the same - // regardless of which side froze it - if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)) - { - return terNO_LINE; - } + return terNO_LINE; } } diff --git a/src/xrpld/app/tx/detail/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp index 8b5ef79b6d4..f6e5f6f3e3f 100644 --- a/src/xrpld/app/tx/detail/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -392,6 +392,7 @@ CashCheck::doApply() false, // authorize account (sleDst->getFlags() & lsfDefaultRipple) == 0, false, // freeze trust line + false, // deep freeze trust line initialBalance, // zero initial balance Issue(currency, account_), // limit of zero 0, // quality in diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 2665851767f..41c99530053 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -257,33 +257,30 @@ CreateOffer::checkAcceptAsset( } } - if (view.rules().enabled(featureDeepFreeze)) + // An account can not create a trustline to itself, so no line can exist + // to be frozen. Additionally, an issuer can always accept its own + // issuance. + if (issue.account == id) { - // An account can not create a trustline to itself, so no line can exist - // to be frozen. Additionally, an issuer can always accept its own - // issuance. - if (issue.account == id) - { - return tesSUCCESS; - } + return tesSUCCESS; + } - auto const trustLine = - view.read(keylet::line(id, issue.account, issue.currency)); + auto const trustLine = + view.read(keylet::line(id, issue.account, issue.currency)); - if (!trustLine) - { - return tesSUCCESS; - } + if (!trustLine) + { + return tesSUCCESS; + } - // There's no difference which side enacted deep freeze, accepting - // tokens shouldn't be possible. - bool const deepFrozen = - (*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze); + // There's no difference which side enacted deep freeze, accepting + // tokens shouldn't be possible. + bool const deepFrozen = + (*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze); - if (deepFrozen) - { - return tecFROZEN; - } + if (deepFrozen) + { + return tecFROZEN; } return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 0332b2b6c00..f9597307c0d 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -87,7 +87,7 @@ SetTrust::preflight(PreflightContext const& ctx) // `tfTrustSetMask`, they are not valid if the amendment is not enabled. if (uTxFlags & (tfSetDeepFreeze | tfClearDeepFreeze)) { - return temINVALID_FLAG; + return temDISABLED; } } @@ -660,6 +660,7 @@ SetTrust::doApply() bSetAuth, bSetNoRipple && !bClearNoRipple, bSetFreeze && !bClearFreeze, + bSetDeepFreeze, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index 74027752486..7114cc11660 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -438,6 +438,7 @@ trustCreate( const bool bAuth, // --> authorize account. const bool bNoRipple, // --> others cannot ripple through const bool bFreeze, // --> funds cannot leave + bool bDeepFreeze, // --> can neither receive nor send funds STAmount const& saBalance, // --> balance of account being set. // Issuer should be noAccount() STAmount const& saLimit, // --> limit for account being set. diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index a8709d6a3b7..ac455a3c390 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -272,11 +272,6 @@ isDeepFrozen( Currency const& currency, AccountID const& issuer) { - if (!view.rules().enabled(featureDeepFreeze)) - { - return false; - } - if (isXRP(currency)) { return false; @@ -898,6 +893,7 @@ trustCreate( const bool bAuth, // --> authorize account. const bool bNoRipple, // --> others cannot ripple through const bool bFreeze, // --> funds cannot leave + bool bDeepFreeze, // --> can neither receive nor send funds STAmount const& saBalance, // --> balance of account being set. // Issuer should be noAccount() STAmount const& saLimit, // --> limit for account being set. @@ -978,7 +974,11 @@ trustCreate( } if (bFreeze) { - uFlags |= (!bSetHigh ? lsfLowFreeze : lsfHighFreeze); + uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze); + } + if (bDeepFreeze) + { + uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze); } if ((slePeer->getFlags() & lsfDefaultRipple) == 0) @@ -1215,6 +1215,7 @@ rippleCreditIOU( false, noRipple, false, + false, saBalance, saReceiverLimit, 0, @@ -1701,6 +1702,7 @@ issueIOU( false, noRipple, false, + false, final_balance, limit, 0, From b5a10eca4abbdfe602a363ede9199a883f2b2cd6 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Tue, 10 Dec 2024 15:38:48 +0000 Subject: [PATCH 18/37] Fixed buy offer crossing when deep frozen --- src/xrpld/app/tx/detail/OfferStream.cpp | 13 +++++++++++++ src/xrpld/ledger/View.h | 7 +++++++ src/xrpld/ledger/detail/View.cpp | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/xrpld/app/tx/detail/OfferStream.cpp b/src/xrpld/app/tx/detail/OfferStream.cpp index b963195259a..5a4dd3b7580 100644 --- a/src/xrpld/app/tx/detail/OfferStream.cpp +++ b/src/xrpld/app/tx/detail/OfferStream.cpp @@ -272,6 +272,19 @@ TOfferStreamBase::step() continue; } + bool const deepFrozen = isDeepFrozen( + view_, + offer_.owner(), + offer_.issueIn().currency, + offer_.issueIn().account); + if (deepFrozen) + { + permRmOffer(entry->key()); + JLOG(j_.trace()) + << "Removing deep frozen unfunded offer " << entry->key(); + continue; + } + // Calculate owner funds ownerFunds_ = accountFundsHelper( view_, diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index 7114cc11660..b964fc0ee76 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -153,6 +153,13 @@ isFrozen(ReadView const& view, AccountID const& account, Asset const& asset) asset.value()); } +[[nodiscard]] bool +isDeepFrozen( + ReadView const& view, + AccountID const& account, + Currency const& currency, + AccountID const& issuer); + // Returns the amount an account can spend without going into debt. // // <-- saAmount: amount of currency held by account. May be negative. diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index ac455a3c390..56ffd78e9b6 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -265,7 +265,7 @@ isFrozen( isIndividualFrozen(view, account, mptIssue); } -static bool +bool isDeepFrozen( ReadView const& view, AccountID const& account, From a1d7dac8c22a1799980dfb7720d2d8e49ae1aa0a Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 6 Jan 2025 12:29:41 +0000 Subject: [PATCH 19/37] Reverted deep freeze check tests --- src/test/app/Check_test.cpp | 67 ------------------------------------- 1 file changed, 67 deletions(-) diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 9739cf36c63..31b45abf43a 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -546,72 +546,6 @@ class Check_test : public beast::unit_test::suite env(trust(alice, USD(0), tfClearFreeze)); env.close(); } - { - // Deep Frozen trust line. Check creation should be similar to - // payment behavior in the face of frozen trust lines. - env.trust(USD(1000), alice); - env.trust(USD(1000), bob); - env.close(); - env(pay(gw1, alice, USD(25))); - env(pay(gw1, bob, USD(25))); - env.close(); - - // Setting trustline deep freeze in one direction prevents alice - // from creating a check for USD. And bob and gw1 should not be - // able to create a check for USD to alice. - env(trust(gw1, alice["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - env.close(); - env(check::create(alice, bob, USD(50)), ter(tecFROZEN)); - env.close(); - env(pay(alice, bob, USD(1)), ter(tecPATH_DRY)); - env.close(); - env(check::create(bob, alice, USD(50))); - env.close(); - if (features[featureDeepFreeze]) - env(pay(bob, alice, USD(1)), ter(tecPATH_DRY)); - else - env(pay(bob, alice, USD(1))); - env.close(); - env(check::create(gw1, alice, USD(50))); - env.close(); - env(pay(gw1, alice, USD(1))); - env.close(); - - // Clear that freeze. Now check creation works. - env(trust(gw1, alice["USD"](0), tfClearFreeze | tfClearDeepFreeze)); - env.close(); - env(check::create(alice, bob, USD(50))); - env.close(); - env(check::create(bob, alice, USD(50))); - env.close(); - env(check::create(gw1, alice, USD(50))); - env.close(); - - // Deep Freezing in the other direction does effect alice's USD - // check creation, and prevents bob and gw1 from writing a check - // for USD to alice. - env(trust(alice, USD(0), tfSetFreeze | tfSetDeepFreeze)); - env.close(); - env(check::create(alice, bob, USD(50))); - env.close(); - if (features[featureDeepFreeze]) - env(pay(alice, bob, USD(1)), ter(tecPATH_DRY)); - else - env(pay(alice, bob, USD(1))); - env.close(); - env(check::create(bob, alice, USD(50)), ter(tecFROZEN)); - env.close(); - env(pay(bob, alice, USD(1)), ter(tecPATH_DRY)); - env.close(); - env(check::create(gw1, alice, USD(50)), ter(tecFROZEN)); - env.close(); - env(pay(gw1, alice, USD(1)), ter(tecPATH_DRY)); - env.close(); - - // Clear that deep freeze. - env(trust(alice, USD(0), tfClearFreeze | tfClearDeepFreeze)); - env.close(); - } // Expired expiration. env(check::create(alice, bob, USD(50)), @@ -2785,7 +2719,6 @@ class Check_test : public beast::unit_test::suite auto const sa = supported_amendments(); testWithFeats(sa - featureCheckCashMakesTrustLine); testWithFeats(sa - disallowIncoming); - testWithFeats(sa - featureDeepFreeze); testWithFeats(sa); testTrustLineCreation(sa); // Test with featureCheckCashMakesTrustLine From c971db5e9f1dc8a3290b8ade49a88efb4c372832 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 10 Jan 2025 15:02:26 +0000 Subject: [PATCH 20/37] Added revamped deep freeze check tests --- include/xrpl/protocol/LedgerFormats.h | 2 +- src/test/app/Freeze_test.cpp | 316 +++++++++++++++++++++++++- 2 files changed, 316 insertions(+), 2 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 0657ce8cf76..c704a2a7aa9 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -165,7 +165,7 @@ enum LedgerSpecificFlags { lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client // apps to identify payments via AMM. lsfLowDeepFreeze = 0x02000000, // True, low side has set deep freeze flag - lsfHighDeepFreeze = 0x04000000, // True, low side has set deep freeze flag + lsfHighDeepFreeze = 0x04000000, // True, high side has set deep freeze flag // ltSIGNER_LIST lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index a9b73795755..65494343327 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -806,7 +806,9 @@ class Freeze_test : public beast::unit_test::suite offers(A1, 0)); // test: cannot create passive sell offer - env(offer(A1, XRP(2), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env(offer(A1, XRP(2), G1["USD"](1)), + txflags(tfPassive), + ter(tecUNFUNDED_OFFER)); env.close(); env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); @@ -1048,6 +1050,310 @@ class Freeze_test : public beast::unit_test::suite } } + void + testChecksWhenFrozen(FeatureBitset features) + { + testcase("Checks on frozen trust lines"); + + using namespace test::jtx; + Env env(*this, features); + + Account G1{"G1"}; + Account A1{"A1"}; + Account A2{"A2"}; + auto const USD{G1["USD"]}; + + env.fund(XRP(10000), G1, A1, A2); + env.close(); + + env.trust(USD(1000), A1, A2); + env.close(); + + env(pay(G1, A1, USD(1000))); + env(pay(G1, A2, USD(1000))); + env.close(); + + // Confirming we can write and cash checks + { + uint256 const checkId{getCheckIndex(G1, env.seq(G1))}; + env(check::create(G1, A1, USD(10))); + env.close(); + env(check::cash(A1, checkId, USD(10))); + env.close(); + } + + { + uint256 const checkId{getCheckIndex(G1, env.seq(G1))}; + env(check::create(G1, A2, USD(10))); + env.close(); + env(check::cash(A2, checkId, USD(10))); + env.close(); + } + + { + uint256 const checkId{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, G1, USD(10))); + env.close(); + env(check::cash(G1, checkId, USD(10))); + env.close(); + } + + { + uint256 const checkId{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, A2, USD(10))); + env.close(); + env(check::cash(A2, checkId, USD(10))); + env.close(); + } + + { + uint256 const checkId{getCheckIndex(A2, env.seq(A2))}; + env(check::create(A2, G1, USD(10))); + env.close(); + env(check::cash(G1, checkId, USD(10))); + env.close(); + } + + { + uint256 const checkId{getCheckIndex(A2, env.seq(A2))}; + env(check::create(A2, A1, USD(10))); + env.close(); + env(check::cash(A1, checkId, USD(10))); + env.close(); + } + + // Testing creation and cashing of checks on a trustline frozen by + // issuer + { + env(trust(G1, A1["USD"](0), tfSetFreeze)); + env.close(); + + // test: issuer writes check to A1. + { + uint256 const checkId{getCheckIndex(G1, env.seq(G1))}; + env(check::create(G1, A1, USD(10))); + env.close(); + // This might be a bug in current check implementation. + // Normal freeze should not prevent A1 balance from increasing. + env(check::cash(A1, checkId, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A2 writes check to A1. + { + uint256 const checkId{getCheckIndex(A2, env.seq(A2))}; + env(check::create(A2, A1, USD(10))); + env.close(); + // Same as previous test + env(check::cash(A1, checkId, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A1 writes check to issuer + { + // This might be another bug in check implementation. + // The creation of the check is not a payment, it's not + // really necessary to prevent the creation of checks. + env(check::create(A1, G1, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A1 writes check to A2 + { + // Same as previous test + env(check::create(A1, A2, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // Unfreeze the trustline to create a couple of checks so that we + // could try to cash them later when the trustline is frozen again. + env(trust(G1, A1["USD"](0), tfClearFreeze)); + env.close(); + + uint256 const checkId1{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, G1, USD(10))); + env.close(); + uint256 const checkId2{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, A2, USD(10))); + env.close(); + + env(trust(G1, A1["USD"](0), tfSetFreeze)); + env.close(); + + // test: issuer tries to cash the check from A1 + { + env(check::cash(G1, checkId1, USD(10)), ter(tecPATH_PARTIAL)); + env.close(); + } + + // test: A2 tries to cash the check from A1 + { + env(check::cash(A2, checkId2, USD(10)), ter(tecPATH_PARTIAL)); + env.close(); + } + + env(trust(G1, A1["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing creation and cashing of checks on a trustline deep frozen by + // issuer + if (features[featureDeepFreeze]) + { + env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: issuer writes check to A1. + { + uint256 const checkId{getCheckIndex(G1, env.seq(G1))}; + env(check::create(G1, A1, USD(10))); + env.close(); + // This might be a bug in current check implementation. + // Normal freeze should not prevent A1 balance from increasing. + env(check::cash(A1, checkId, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A2 writes check to A1. + { + uint256 const checkId{getCheckIndex(A2, env.seq(A2))}; + env(check::create(A2, A1, USD(10))); + env.close(); + // Same as previous test + env(check::cash(A1, checkId, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A1 writes check to issuer + { + // This might be another bug in check implementation. + // The creation of the check is not a payment, it's not + // really necessary to prevent the creation of checks. + env(check::create(A1, G1, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A1 writes check to A2 + { + // Same as previous test + env(check::create(A1, A2, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // Unfreeze the trustline to create a couple of checks so that we + // could try to cash them later when the trustline is frozen again. + env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + + uint256 const checkId1{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, G1, USD(10))); + env.close(); + uint256 const checkId2{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, A2, USD(10))); + env.close(); + + env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: issuer tries to cash the check from A1 + { + env(check::cash(G1, checkId1, USD(10)), ter(tecPATH_PARTIAL)); + env.close(); + } + + // test: A2 tries to cash the check from A1 + { + env(check::cash(A2, checkId2, USD(10)), ter(tecPATH_PARTIAL)); + env.close(); + } + + env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + + // Testing creation and cashing of checks on a trustline frozen by + // a currency holder + { + env(trust(A1, G1["USD"](0), tfSetFreeze)); + env.close(); + + // test: issuer writes check to A1. + { + env(check::create(G1, A1, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A2 writes check to A1. + { + env(check::create(A2, A1, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A1 writes check to issuer + { + uint256 const checkId{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, G1, USD(10))); + env.close(); + env(check::cash(G1, checkId, USD(10))); + env.close(); + } + + // test: A1 writes check to A2 + { + uint256 const checkId{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, A2, USD(10))); + env.close(); + env(check::cash(A2, checkId, USD(10))); + env.close(); + } + + env(trust(A1, G1["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing creation and cashing of checks on a trustline deep frozen by + // a currency holder + if (features[featureDeepFreeze]) + { + env(trust(A1, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: issuer writes check to A1. + { + env(check::create(G1, A1, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A2 writes check to A1. + { + env(check::create(A2, A1, USD(10)), ter(tecFROZEN)); + env.close(); + } + + // test: A1 writes check to issuer + { + uint256 const checkId{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, G1, USD(10))); + env.close(); + env(check::cash(G1, checkId, USD(10)), ter(tecPATH_PARTIAL)); + env.close(); + } + + // test: A1 writes check to A2 + { + uint256 const checkId{getCheckIndex(A1, env.seq(A1))}; + env(check::create(A1, A2, USD(10))); + env.close(); + env(check::cash(A2, checkId, USD(10)), ter(tecPATH_PARTIAL)); + env.close(); + } + + env(trust(A1, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + } + + // Helper function to extract trustline flags from open ledger uint32_t getTrustlineFlags( test::jtx::Env& env, @@ -1073,6 +1379,13 @@ class Freeze_test : public beast::unit_test::suite .asUInt(); } + // Helper function that returns the index of the next check on account + uint256 + getCheckIndex(AccountID const& account, std::uint32_t uSequence) + { + return keylet::check(account, uSequence).key; + } + public: void run() override @@ -1087,6 +1400,7 @@ class Freeze_test : public beast::unit_test::suite testOffersWhenFrozen(features); testOffersWhenDeepFrozen(features); testPaymentsWhenDeepFrozen(features); + testChecksWhenFrozen(features); }; using namespace test::jtx; auto const sa = supported_amendments(); From 9f17d9fec94114886f32aba8ecd5c516f0e70ae1 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 13 Jan 2025 14:58:10 +0000 Subject: [PATCH 21/37] Reverted deep freeze AMM tests --- src/test/app/AMMExtended_test.cpp | 4 ---- src/test/app/AMM_test.cpp | 23 ----------------------- 2 files changed, 27 deletions(-) diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 0c04bec8fea..96053b93b44 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -4129,10 +4129,6 @@ struct AMMExtended_test : public jtx::AMMTest { using namespace test::jtx; auto const sa = supported_amendments(); - testRippleState(sa - featureDeepFreeze); - testGlobalFreeze(sa - featureDeepFreeze); - testOffersWhenFrozen(sa - featureDeepFreeze); - testRippleState(sa); testGlobalFreeze(sa); testOffersWhenFrozen(sa); diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 24b6ff668ec..ceddc019504 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -4397,28 +4397,6 @@ struct AMM_test : public jtx::AMMTest 0, std::nullopt, {features}); - - // Individually deep frozen account - if (features[featureDeepFreeze]) - { - testAMM( - [&](AMM& ammAlice, Env& env) { - env(trust( - gw, carol["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - env(trust( - gw, alice["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - env.close(); - env(pay(alice, carol, USD(1)), - path(~USD), - sendmax(XRP(10)), - txflags(tfNoRippleDirect | tfPartialPayment), - ter(tecPATH_DRY)); - }, - std::nullopt, - 0, - std::nullopt, - {features}); - } } void @@ -6904,7 +6882,6 @@ struct AMM_test : public jtx::AMMTest testBasicPaymentEngine(all - fixAMMv1_1); testBasicPaymentEngine(all - fixReducedOffersV2); testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2); - testBasicPaymentEngine(all - featureDeepFreeze); testAMMTokens(); testAmendment(); testFlags(); From d75894a994c807cbefcd0621aa4e4441aacd3d58 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Wed, 15 Jan 2025 15:46:58 +0000 Subject: [PATCH 22/37] Added some AMM tests and fixed comments --- src/test/app/Freeze_test.cpp | 266 ++++++++++++++++-------- src/xrpld/app/tx/detail/OfferStream.cpp | 3 +- src/xrpld/app/tx/detail/SetTrust.cpp | 10 +- src/xrpld/ledger/detail/View.cpp | 9 +- 4 files changed, 192 insertions(+), 96 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 65494343327..eb83a411d64 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -17,6 +17,7 @@ */ //============================================================================== #include +#include #include #include #include @@ -590,12 +591,13 @@ class Freeze_test : public beast::unit_test::suite env.require(flags(G1, asfNoFreeze)); env.require(flags(G1, asfGlobalFreeze)); - // test: trustlines can't be frozen + // test: trustlines can't be frozen when no freeze enacted if (features[featureDeepFreeze]) { env(trust(G1, A1["USD"](0), tfSetFreeze), ter(tecNO_PERMISSION)); - // test: cannot deep freeze already frozen line + // test: cannot deep freeze already frozen line when no freeze + // enacted env(trust(G1, frozenAcc["USD"](0), tfSetDeepFreeze), ter(tecNO_PERMISSION)); } @@ -752,6 +754,7 @@ class Freeze_test : public beast::unit_test::suite Account A1{"A1"}; Account A2{"A2"}; Account A3{"A3"}; + auto const USD{G1["USD"]}; env.fund(XRP(10000), G1, A1, A2, A3); env.close(); @@ -759,29 +762,29 @@ class Freeze_test : public beast::unit_test::suite env.trust(G1["USD"](1000), A1, A2, A3); env.close(); - env(pay(G1, A1, G1["USD"](1000))); - env(pay(G1, A2, G1["USD"](1000))); + env(pay(G1, A1, USD(1000))); + env(pay(G1, A2, USD(1000))); env.close(); // Making large passive sell offer // Wants to sell 50 USD for 100 XRP - env(offer(A2, XRP(100), G1["USD"](50)), txflags(tfPassive)); + env(offer(A2, XRP(100), USD(50)), txflags(tfPassive)); env.close(); // Making large passive buy offer // Wants to buy 100 USD for 100 XRP - env(offer(A3, G1["USD"](100), XRP(100)), txflags(tfPassive)); + env(offer(A3, USD(100), XRP(100)), txflags(tfPassive)); env.close(); env.require(offers(A2, 1), offers(A3, 1)); // Checking A1 can buy from A2 by crossing it's offer - env(offer(A1, G1["USD"](1), XRP(2))); + env(offer(A1, USD(1), XRP(2))); env.close(); - env.require(balance(A1, G1["USD"](1001)), balance(A2, G1["USD"](999))); + env.require(balance(A1, USD(1001)), balance(A2, USD(999))); // Checking A1 can sell to A3 by crossing it's offer - env(offer(A1, XRP(1), G1["USD"](1))); + env(offer(A1, XRP(1), USD(1))); env.close(); - env.require(balance(A1, G1["USD"](1000)), balance(A3, G1["USD"](1))); + env.require(balance(A1, USD(1000)), balance(A3, USD(1))); // Testing aggressive and passive offer placing A1 frozen by issuer { @@ -789,33 +792,31 @@ class Freeze_test : public beast::unit_test::suite env.close(); // test: can still make passive buy offer - env(offer(A1, G1["USD"](1), XRP(0.5)), txflags(tfPassive)); + env(offer(A1, USD(1), XRP(0.5)), txflags(tfPassive)); env.close(); - env.require(balance(A1, G1["USD"](1000)), offers(A1, 1)); + env.require(balance(A1, USD(1000)), offers(A1, 1)); // Cleanup env(offer_cancel(A1, env.seq(A1) - 1)); env.require(offers(A1, 0)); env.close(); // test: can still buy from A2 - env(offer(A1, G1["USD"](1), XRP(2))); + env(offer(A1, USD(1), XRP(2))); env.close(); env.require( - balance(A1, G1["USD"](1001)), - balance(A2, G1["USD"](998)), - offers(A1, 0)); + balance(A1, USD(1001)), balance(A2, USD(998)), offers(A1, 0)); // test: cannot create passive sell offer - env(offer(A1, XRP(2), G1["USD"](1)), + env(offer(A1, XRP(2), USD(1)), txflags(tfPassive), ter(tecUNFUNDED_OFFER)); env.close(); - env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); + env.require(balance(A1, USD(1001)), offers(A1, 0)); // test: cannot sell to A3 - env(offer(A1, XRP(1), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env(offer(A1, XRP(1), USD(1)), ter(tecUNFUNDED_OFFER)); env.close(); - env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); + env.require(balance(A1, USD(1001)), offers(A1, 0)); env(trust(G1, A1["USD"](0), tfClearFreeze)); env.close(); @@ -828,60 +829,53 @@ class Freeze_test : public beast::unit_test::suite env.close(); // test: cannot create passive buy offer - env(offer(A1, G1["USD"](1), XRP(0.5)), ter(tecFROZEN)); + env(offer(A1, USD(1), XRP(0.5)), ter(tecFROZEN)); env.close(); // test: cannot buy from A2 - env(offer(A1, G1["USD"](1), XRP(2)), ter(tecFROZEN)); + env(offer(A1, USD(1), XRP(2)), ter(tecFROZEN)); env.close(); // test: cannot create passive sell offer - env(offer(A1, XRP(2), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env(offer(A1, XRP(2), USD(1)), ter(tecUNFUNDED_OFFER)); env.close(); // test: cannot sell to A3 - env(offer(A1, XRP(1), G1["USD"](1)), ter(tecUNFUNDED_OFFER)); + env(offer(A1, XRP(1), USD(1)), ter(tecUNFUNDED_OFFER)); env.close(); - env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(G1, USD(0), tfClearFreeze | tfClearDeepFreeze)); env.close(); - env.require(balance(A1, G1["USD"](1001)), offers(A1, 0)); + env.require(balance(A1, USD(1001)), offers(A1, 0)); } // Testing already existing offers behavior after trustline is frozen { - env.require(balance(A1, G1["USD"](1001))); - env(offer(A1, XRP(1.9), G1["USD"](1))); - env(offer(A1, G1["USD"](1), XRP(1.1))); + env.require(balance(A1, USD(1001))); + env(offer(A1, XRP(1.9), USD(1))); + env(offer(A1, USD(1), XRP(1.1))); env.close(); - env.require(balance(A1, G1["USD"](1001)), offers(A1, 2)); + env.require(balance(A1, USD(1001)), offers(A1, 2)); env(trust(G1, A1["USD"](0), tfSetFreeze)); env.close(); // A2 wants to sell to A1, must succeed - env.require( - balance(A1, G1["USD"](1001)), balance(A2, G1["USD"](998))); - env(offer(A2, XRP(1.1), G1["USD"](1)), txflags(tfFillOrKill)); + env.require(balance(A1, USD(1001)), balance(A2, USD(998))); + env(offer(A2, XRP(1.1), USD(1)), txflags(tfFillOrKill)); env.close(); env.require( - balance(A1, G1["USD"](1002)), - balance(A2, G1["USD"](997)), - offers(A1, 1)); + balance(A1, USD(1002)), balance(A2, USD(997)), offers(A1, 1)); // A3 wants to buy from A1, must fail env.require( - balance(A1, G1["USD"](1002)), - balance(A3, G1["USD"](1)), - offers(A1, 1)); - env(offer(A3, G1["USD"](1), XRP(1.9)), + balance(A1, USD(1002)), balance(A3, USD(1)), offers(A1, 1)); + env(offer(A3, USD(1), XRP(1.9)), txflags(tfFillOrKill), ter(tecKILLED)); env.close(); env.require( - balance(A1, G1["USD"](1002)), - balance(A3, G1["USD"](1)), - offers(A1, 0)); + balance(A1, USD(1002)), balance(A3, USD(1)), offers(A1, 0)); env(trust(G1, A1["USD"](0), tfClearFreeze)); env.close(); @@ -890,40 +884,33 @@ class Freeze_test : public beast::unit_test::suite // Testing existing offers behavior after trustline is deep frozen if (features[featureDeepFreeze]) { - env.require(balance(A1, G1["USD"](1002))); - env(offer(A1, XRP(1.9), G1["USD"](1))); - env(offer(A1, G1["USD"](1), XRP(1.1))); + env.require(balance(A1, USD(1002))); + env(offer(A1, XRP(1.9), USD(1))); + env(offer(A1, USD(1), XRP(1.1))); env.close(); - env.require(balance(A1, G1["USD"](1002)), offers(A1, 2)); + env.require(balance(A1, USD(1002)), offers(A1, 2)); env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); env.close(); // A2 wants to sell to A1, must fail - env.require( - balance(A1, G1["USD"](1002)), balance(A2, G1["USD"](997))); - env(offer(A2, XRP(1.1), G1["USD"](1)), + env.require(balance(A1, USD(1002)), balance(A2, USD(997))); + env(offer(A2, XRP(1.1), USD(1)), txflags(tfFillOrKill), ter(tecKILLED)); env.close(); env.require( - balance(A1, G1["USD"](1002)), - balance(A2, G1["USD"](997)), - offers(A1, 1)); + balance(A1, USD(1002)), balance(A2, USD(997)), offers(A1, 1)); // A3 wants to buy from A1, must fail env.require( - balance(A1, G1["USD"](1002)), - balance(A3, G1["USD"](1)), - offers(A1, 1)); - env(offer(A3, G1["USD"](1), XRP(1.9)), + balance(A1, USD(1002)), balance(A3, USD(1)), offers(A1, 1)); + env(offer(A3, USD(1), XRP(1.9)), txflags(tfFillOrKill), ter(tecKILLED)); env.close(); env.require( - balance(A1, G1["USD"](1002)), - balance(A3, G1["USD"](1)), - offers(A1, 0)); + balance(A1, USD(1002)), balance(A3, USD(1)), offers(A1, 0)); env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); env.close(); @@ -941,6 +928,7 @@ class Freeze_test : public beast::unit_test::suite Account G1{"G1"}; Account A1{"A1"}; Account A2{"A2"}; + auto const USD{G1["USD"]}; env.fund(XRP(10000), G1, A1, A2); env.close(); @@ -948,19 +936,19 @@ class Freeze_test : public beast::unit_test::suite env.trust(G1["USD"](1000), A1, A2); env.close(); - env(pay(G1, A1, G1["USD"](1000))); - env(pay(G1, A2, G1["USD"](1000))); + env(pay(G1, A1, USD(1000))); + env(pay(G1, A2, USD(1000))); env.close(); // Checking payments before freeze // To issuer: - env(pay(A1, G1, G1["USD"](1))); - env(pay(A2, G1, G1["USD"](1))); + env(pay(A1, G1, USD(1))); + env(pay(A2, G1, USD(1))); env.close(); // To each other: - env(pay(A1, A2, G1["USD"](1))); - env(pay(A2, A1, G1["USD"](1))); + env(pay(A1, A2, USD(1))); + env(pay(A2, A1, USD(1))); env.close(); // Freeze A1 @@ -968,15 +956,15 @@ class Freeze_test : public beast::unit_test::suite env.close(); // Issuer and A1 can send payments to each other - env(pay(A1, G1, G1["USD"](1))); - env(pay(G1, A1, G1["USD"](1))); + env(pay(A1, G1, USD(1))); + env(pay(G1, A1, USD(1))); env.close(); // A1 cannot send tokens to A2 - env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(A1, A2, USD(1)), ter(tecPATH_DRY)); // A2 can still send to A1 - env(pay(A2, A1, G1["USD"](1))); + env(pay(A2, A1, USD(1))); env.close(); if (features[featureDeepFreeze]) @@ -986,15 +974,15 @@ class Freeze_test : public beast::unit_test::suite env.close(); // Issuer and A1 can send payments to each other - env(pay(A1, G1, G1["USD"](1))); - env(pay(G1, A1, G1["USD"](1))); + env(pay(A1, G1, USD(1))); + env(pay(G1, A1, USD(1))); env.close(); // A1 cannot send tokens to A2 - env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(A1, A2, USD(1)), ter(tecPATH_DRY)); // A2 cannot send tokens to A1 - env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(A2, A1, USD(1)), ter(tecPATH_DRY)); // Clear deep freeze on A1 env(trust(G1, A1["USD"](0), tfClearDeepFreeze)); @@ -1010,21 +998,21 @@ class Freeze_test : public beast::unit_test::suite env.close(); // Issuer and A2 must not be affected - env(pay(A2, G1, G1["USD"](1))); - env(pay(G1, A2, G1["USD"](1))); + env(pay(A2, G1, USD(1))); + env(pay(G1, A2, USD(1))); env.close(); // A1 can send tokens to the issuer - env(pay(A1, G1, G1["USD"](1))); + env(pay(A1, G1, USD(1))); env.close(); // A1 can send tokens to A2 - env(pay(A1, A2, G1["USD"](1))); + env(pay(A1, A2, USD(1))); env.close(); // Issuer cannot sent tokens to A1 - env(pay(G1, A1, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(G1, A1, USD(1)), ter(tecPATH_DRY)); // A2 cannot send tokens to A1 - env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(A2, A1, USD(1)), ter(tecPATH_DRY)); if (features[featureDeepFreeze]) { @@ -1033,20 +1021,20 @@ class Freeze_test : public beast::unit_test::suite env.close(); // Issuer and A2 must not be affected - env(pay(A2, G1, G1["USD"](1))); - env(pay(G1, A2, G1["USD"](1))); + env(pay(A2, G1, USD(1))); + env(pay(G1, A2, USD(1))); env.close(); // A1 can still send token to issuer - env(pay(A1, G1, G1["USD"](1))); + env(pay(A1, G1, USD(1))); env.close(); // Issuer cannot send tokens to A1 - env(pay(G1, A1, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(G1, A1, USD(1)), ter(tecPATH_DRY)); // A2 cannot send tokens to A1 - env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(A2, A1, USD(1)), ter(tecPATH_DRY)); // A1 cannot send tokens to A2 - env(pay(A1, A2, G1["USD"](1)), ter(tecPATH_DRY)); + env(pay(A1, A2, USD(1)), ter(tecPATH_DRY)); } } @@ -1353,6 +1341,111 @@ class Freeze_test : public beast::unit_test::suite } } + void + testAMMWhenFreeze(FeatureBitset features) + { + testcase("AMM payments on frozen trust lines"); + using namespace test::jtx; + + Env env(*this, features); + Account G1{"G1"}; + Account A1{"A1"}; + Account A2{"A2"}; + auto const USD{G1["USD"]}; + + env.fund(XRP(10000), G1, A1, A2); + env.close(); + + env.trust(G1["USD"](10000), A1, A2); + env.close(); + + env(pay(G1, A1, USD(1000))); + env(pay(G1, A2, USD(1000))); + env.close(); + + AMM ammG1(env, G1, XRP(1'000), USD(1'000)); + env.close(); + + // Testing basic payment using AMM when freezing one of the trust lines. + { + env(trust(G1, A1["USD"](0), tfSetFreeze)); + env.close(); + + // test: can still use XRP to make payment + env(pay(A1, A2, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect)); + env.close(); + + // test: cannot use USD to make payment + env(pay(A1, A2, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_DRY)); + env.close(); + + // test: can still receive USD payments. + env(pay(A2, A1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect)); + env.close(); + + // test: can still receive XRP payments. + env(pay(A2, A1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect)); + env.close(); + + env(trust(G1, A1["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing basic payment using AMM when deep freezing one of the trust + // lines. + if (features[featureDeepFreeze]) + { + env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: can still use XRP to make payment + env(pay(A1, A2, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect)); + env.close(); + + // test: cannot use USD to make payment + env(pay(A1, A2, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_DRY)); + env.close(); + + // test: cannot receive USD payments. + env(pay(A2, A1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_DRY)); + env.close(); + + // test: can still receive XRP payments. + env(pay(A2, A1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect)); + env.close(); + + env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + } + // Helper function to extract trustline flags from open ledger uint32_t getTrustlineFlags( @@ -1401,6 +1494,7 @@ class Freeze_test : public beast::unit_test::suite testOffersWhenDeepFrozen(features); testPaymentsWhenDeepFrozen(features); testChecksWhenFrozen(features); + testAMMWhenFreeze(features); }; using namespace test::jtx; auto const sa = supported_amendments(); diff --git a/src/xrpld/app/tx/detail/OfferStream.cpp b/src/xrpld/app/tx/detail/OfferStream.cpp index 5a4dd3b7580..e0ab7963b60 100644 --- a/src/xrpld/app/tx/detail/OfferStream.cpp +++ b/src/xrpld/app/tx/detail/OfferStream.cpp @@ -279,9 +279,10 @@ TOfferStreamBase::step() offer_.issueIn().account); if (deepFrozen) { - permRmOffer(entry->key()); JLOG(j_.trace()) << "Removing deep frozen unfunded offer " << entry->key(); + permRmOffer(entry->key()); + offer_ = TOffer{}; continue; } diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index f9597307c0d..b4c7d90b10f 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -235,14 +235,12 @@ SetTrust::preclaim(PreclaimContext const& ctx) bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze); - if (bNoFreeze) + if (bNoFreeze && (bSetFreeze || bSetDeepFreeze)) { - if (bSetFreeze || bSetDeepFreeze) - { - // Cannot freeze the trust line if NoFreeze is set - return tecNO_PERMISSION; - } + // Cannot freeze the trust line if NoFreeze is set + return tecNO_PERMISSION; } + bool const bClearFreeze = (uTxFlags & tfClearFreeze); bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze); if ((bSetFreeze || bSetDeepFreeze) && diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 56ffd78e9b6..7b549a0a681 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -282,10 +282,13 @@ isDeepFrozen( return false; } - // Check if the line is deep frozen auto const sle = view.read(keylet::line(account, issuer, currency)); - return sle && - (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze)); + if (!sle) + { + return false; + } + + return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze); } STAmount From 03f7b5db0cbf97b616f5c82b3668a6230e2abf3c Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Wed, 15 Jan 2025 16:38:11 +0000 Subject: [PATCH 23/37] Added offer tests with trustline frozen by currency holder --- src/test/app/Freeze_test.cpp | 106 +++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index eb83a411d64..16b3f3c9aca 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -759,7 +759,7 @@ class Freeze_test : public beast::unit_test::suite env.fund(XRP(10000), G1, A1, A2, A3); env.close(); - env.trust(G1["USD"](1000), A1, A2, A3); + env.trust(G1["USD"](10000), A1, A2, A3); env.close(); env(pay(G1, A1, USD(1000))); @@ -786,7 +786,8 @@ class Freeze_test : public beast::unit_test::suite env.close(); env.require(balance(A1, USD(1000)), balance(A3, USD(1))); - // Testing aggressive and passive offer placing A1 frozen by issuer + // Testing aggressive and passive offer placing, trustline frozen by + // the issuer { env(trust(G1, A1["USD"](0), tfSetFreeze)); env.close(); @@ -822,7 +823,8 @@ class Freeze_test : public beast::unit_test::suite env.close(); } - // Testing aggressive and passive offer placing A1 deep frozen by issuer + // Testing aggressive and passive offer placing, trustline deep frozen + // by the issuer if (features[featureDeepFreeze]) { env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); @@ -844,12 +846,13 @@ class Freeze_test : public beast::unit_test::suite env(offer(A1, XRP(1), USD(1)), ter(tecUNFUNDED_OFFER)); env.close(); - env(trust(G1, USD(0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); env.close(); env.require(balance(A1, USD(1001)), offers(A1, 0)); } - // Testing already existing offers behavior after trustline is frozen + // Testing already existing offers behavior after trustline is frozen by + // the issuer { env.require(balance(A1, USD(1001))); env(offer(A1, XRP(1.9), USD(1))); @@ -860,14 +863,14 @@ class Freeze_test : public beast::unit_test::suite env(trust(G1, A1["USD"](0), tfSetFreeze)); env.close(); - // A2 wants to sell to A1, must succeed + // test: A2 wants to sell to A1, must succeed env.require(balance(A1, USD(1001)), balance(A2, USD(998))); env(offer(A2, XRP(1.1), USD(1)), txflags(tfFillOrKill)); env.close(); env.require( balance(A1, USD(1002)), balance(A2, USD(997)), offers(A1, 1)); - // A3 wants to buy from A1, must fail + // test: A3 wants to buy from A1, must fail env.require( balance(A1, USD(1002)), balance(A3, USD(1)), offers(A1, 1)); env(offer(A3, USD(1), XRP(1.9)), @@ -881,7 +884,8 @@ class Freeze_test : public beast::unit_test::suite env.close(); } - // Testing existing offers behavior after trustline is deep frozen + // Testing existing offers behavior after trustline is deep frozen by + // the issuer if (features[featureDeepFreeze]) { env.require(balance(A1, USD(1002))); @@ -893,7 +897,7 @@ class Freeze_test : public beast::unit_test::suite env(trust(G1, A1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); env.close(); - // A2 wants to sell to A1, must fail + // test: A2 wants to sell to A1, must fail env.require(balance(A1, USD(1002)), balance(A2, USD(997))); env(offer(A2, XRP(1.1), USD(1)), txflags(tfFillOrKill), @@ -902,7 +906,7 @@ class Freeze_test : public beast::unit_test::suite env.require( balance(A1, USD(1002)), balance(A2, USD(997)), offers(A1, 1)); - // A3 wants to buy from A1, must fail + // test: A3 wants to buy from A1, must fail env.require( balance(A1, USD(1002)), balance(A3, USD(1)), offers(A1, 1)); env(offer(A3, USD(1), XRP(1.9)), @@ -915,6 +919,88 @@ class Freeze_test : public beast::unit_test::suite env(trust(G1, A1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); env.close(); } + + // Testing aggressive and passive offer placing, trustline frozen by + // the holder + { + env(trust(A1, G1["USD"](0), tfSetFreeze)); + env.close(); + + // test: A1 can make passive buy offer + // TODO: I expected that it would not possible to create buy offer + // in this situation. Fix later. + env(offer(A1, USD(1), XRP(0.5)), txflags(tfPassive)); + env.close(); + env.require(balance(A1, USD(1002)), offers(A1, 1)); + // Cleanup + env(offer_cancel(A1, env.seq(A1) - 1)); + env.require(offers(A1, 0)); + env.close(); + + // test: A1 wants to buy, must fail + if (features[featureFlowCross]) + { + env(offer(A1, USD(1), XRP(2)), + txflags(tfFillOrKill), + ter(tecKILLED)); + env.close(); + env.require( + balance(A1, USD(1002)), + balance(A2, USD(997)), + offers(A1, 0)); + } + else + { + // The transaction that should be here would succeed. + // I don't want to adjust balances in following tests. Flow + // cross feature flag is not relevant to this particular test + // case so we're not missing out some corner cases checks. + } + + // test: A1 can create passive sell offer + env(offer(A1, XRP(2), USD(1)), txflags(tfPassive)); + env.close(); + env.require(balance(A1, USD(1002)), offers(A1, 1)); + // Cleanup + env(offer_cancel(A1, env.seq(A1) - 1)); + env.require(offers(A1, 0)); + env.close(); + + // test: A1 can sell to A3 + env(offer(A1, XRP(1), USD(1)), txflags(tfFillOrKill)); + env.close(); + env.require(balance(A1, USD(1001)), offers(A1, 0)); + + env(trust(A1, G1["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing aggressive and passive offer placing, trustline deep frozen + // by the holder + if (features[featureDeepFreeze]) + { + env(trust(A1, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: A1 cannot create passive buy offer + env(offer(A1, USD(1), XRP(0.5)), ter(tecFROZEN)); + env.close(); + + // test: A1 cannot buy, must fail + env(offer(A1, USD(1), XRP(2)), ter(tecFROZEN)); + env.close(); + + // test: A1 cannot create passive sell offer + env(offer(A1, XRP(2), USD(1)), ter(tecUNFUNDED_OFFER)); + env.close(); + + // test: A1 cannot sell to A3 + env(offer(A1, XRP(1), USD(1)), ter(tecUNFUNDED_OFFER)); + env.close(); + + env(trust(A1, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } } void From ae5fae2661420fb2f7891966bb65260c49a6691f Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 17 Jan 2025 13:38:03 +0000 Subject: [PATCH 24/37] Added longer path payment tests --- src/test/app/Freeze_test.cpp | 248 +++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 16b3f3c9aca..b16e7fe9948 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1003,6 +1003,252 @@ class Freeze_test : public beast::unit_test::suite } } + void + testLongerPathsWhenFrozen(FeatureBitset features) + { + testcase("Longer paths payment on frozen trust lines"); + using namespace test::jtx; + + Env env(*this, features); + Account G1{"G1"}; + Account A1{"A1"}; + Account A2{"A2"}; + auto const USD{G1["USD"]}; + + env.fund(XRP(10000), G1, A1, A2); + env.close(); + + env.trust(G1["USD"](10000), A1, A2); + env.close(); + + env(pay(G1, A1, USD(1000))); + env(pay(G1, A2, USD(1000))); + env.close(); + + env(offer(A2, XRP(100), USD(100)), txflags(tfPassive)); + env.close(); + + // Testing payments A1 <-> G1 using offer from A2 frozen by issuer. + { + env(trust(G1, A2["USD"](0), tfSetFreeze)); + env.close(); + + // test: A1 cannot send USD using XRP through A2 offer + env(pay(A1, G1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + // test: G1 cannot send USD using XRP through A2 offer + env(pay(G1, A1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + env(trust(G1, A2["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing payments A1 <-> G1 using offer from A2 deep frozen by issuer. + if (features[featureDeepFreeze]) + { + env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: A1 cannot send USD using XRP through A2 offer + env(pay(A1, G1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + // test: G1 cannot send USD using XRP through A2 offer + env(pay(G1, A1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + env(trust(G1, A2["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + + // Testing payments A1 <-> G1 using offer from A2 frozen by currency + // holder. + { + env(trust(A2, G1["USD"](0), tfSetFreeze)); + env.close(); + + // test: A1 can send USD using XRP through A2 offer + env(pay(A1, G1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect)); + env.close(); + + // test: G1 can send USD using XRP through A2 offer + env(pay(G1, A1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect)); + env.close(); + + env(trust(A2, G1["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing payments A1 <-> G1 using offer from A2 deep frozen by + // currency holder. + if (features[featureDeepFreeze]) + { + env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: A1 cannot send USD using XRP through A2 offer + env(pay(A1, G1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + // test: G1 cannot send USD using XRP through A2 offer + env(pay(G1, A1, USD(10)), + path(~USD), + sendmax(XRP(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + + // Cleanup + env(offer_cancel(A1, env.seq(A1) - 1)); + env.require(offers(A1, 0)); + env.close(); + + env(offer(A2, USD(100), XRP(100)), txflags(tfPassive)); + env.close(); + + // Testing payments A1 <-> G1 using offer from A2 frozen by issuer. + { + env(trust(G1, A2["USD"](0), tfSetFreeze)); + env.close(); + + // test: A1 can send XRP using USD through A2 offer + env(pay(A1, G1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect)); + env.close(); + + // test: G1 can send XRP using USD through A2 offer + env(pay(G1, A1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect)); + env.close(); + + env(trust(G1, A2["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing payments A1 <-> G1 using offer from A2 deep frozen by + // issuer. + if (features[featureDeepFreeze]) + { + env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: A1 cannot send XRP using USD through A2 offer + env(pay(A1, G1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + // test: G1 cannot send XRP using USD through A2 offer + env(pay(G1, A1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + env(trust(G1, A2["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + + // Testing payments A1 <-> G1 using offer from A2 frozen by currency + // holder. + { + env(trust(A2, G1["USD"](0), tfSetFreeze)); + env.close(); + + // test: A1 can send XRP using USD through A2 offer + // TODO: This might be not a correct behavior. A2 doesn't want to + // receive USDs. + env(pay(A1, G1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect)); + env.close(); + + // test: G1 cannot send XRP using USD through A2 offer + // TODO: This might be not a correct behavior. A2 doesn't want to + // receive USDs. + env(pay(G1, A1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect)); + env.close(); + + env(trust(A2, G1["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing payments A1 <-> G1 using offer from A2 deep frozen by + // currency holder. + if (features[featureDeepFreeze]) + { + env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: A1 cannot send XRP using USD through A2 offer + env(pay(A1, G1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + // test: G1 cannot send XRP using USD through A2 offer + env(pay(G1, A1, XRP(10)), + path(~XRP), + sendmax(USD(11)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + env.close(); + + env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + + // Cleanup + env(offer_cancel(A1, env.seq(A1) - 1)); + env.require(offers(A1, 0)); + env.close(); + } + void testPaymentsWhenDeepFrozen(FeatureBitset features) { @@ -1581,9 +1827,11 @@ class Freeze_test : public beast::unit_test::suite testPaymentsWhenDeepFrozen(features); testChecksWhenFrozen(features); testAMMWhenFreeze(features); + testLongerPathsWhenFrozen(features); }; using namespace test::jtx; auto const sa = supported_amendments(); + testAll(sa - featureFlowCross - featureDeepFreeze); testAll(sa - featureFlowCross); testAll(sa - featureDeepFreeze); testAll(sa); From c60d4914acc72f1b785d441881a8c1a4a87a78a8 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 17 Jan 2025 15:37:13 +0000 Subject: [PATCH 25/37] Added NFT deep freeze tests --- src/test/app/Freeze_test.cpp | 168 +++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index b16e7fe9948..721e25c2bb9 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1778,6 +1778,153 @@ class Freeze_test : public beast::unit_test::suite } } + void + testNFTOffersWhenFreeze(FeatureBitset features) + { + testcase("NFT offers on frozen trust lines"); + using namespace test::jtx; + + Env env(*this, features); + Account G1{"G1"}; + Account A1{"A1"}; + Account A2{"A2"}; + auto const USD{G1["USD"]}; + + env.fund(XRP(10000), G1, A1, A2); + env.close(); + + env.trust(G1["USD"](10000), A1, A2); + env.close(); + + env(pay(G1, A1, USD(1000))); + env(pay(G1, A2, USD(1000))); + env.close(); + + // Testing A2 nft offer sell when A2 frozen by issuer + { + auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + env(trust(G1, A2["USD"](0), tfSetFreeze)); + env.close(); + + // test: A2 can still receive USD for his NFT + env(token::acceptSellOffer(A1, sellOfferIndex)); + env.close(); + + env(trust(G1, A2["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing A2 nft offer sell when A2 deep frozen by issuer + // if (features[featureDeepFreeze]) + // { + // auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + + // env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + // env.close(); + + // // test: A2 cannot receive USD for his NFT + // env(token::acceptSellOffer(A1, sellOfferIndex), + // ter(tecINSUFFICIENT_FUNDS)); + // env.close(); + + // env(trust(G1, A2["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + // env.close(); + // } + + // Testing A1 nft offer sell when A2 frozen by issuer + { + auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + env(trust(G1, A2["USD"](0), tfSetFreeze)); + env.close(); + + // test: A2 cannot send USD for NFT + env(token::acceptSellOffer(A2, sellOfferIndex), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + + env(trust(G1, A2["USD"](0), tfClearFreeze)); + env.close(); + } + + // Testing A1 nft offer sell when A2 deep frozen by issuer + if (features[featureDeepFreeze]) + { + auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: A2 cannot send USD for NFT + env(token::acceptSellOffer(A2, sellOfferIndex), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + + env(trust(G1, A2["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + + // // Testing A2 nft offer sell when A2 frozen by currency holder + // { + // auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + // env(trust(A2, G1["USD"](0), tfSetFreeze)); + // env.close(); + + // // test: A2 cannot receive USD for his NFT + // env(token::acceptSellOffer(A1, sellOfferIndex), + // ter(tecINSUFFICIENT_FUNDS)); + // env.close(); + + // env(trust(A2, G1["USD"](0), tfClearFreeze)); + // env.close(); + // } + + // // Testing A2 nft offer sell when A2 deep frozen by currency holder + // if (features[featureDeepFreeze]) + // { + // auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + + // env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + // env.close(); + + // // test: A2 cannot receive USD for his NFT + // env(token::acceptSellOffer(A1, sellOfferIndex), + // ter(tecINSUFFICIENT_FUNDS)); + // env.close(); + + // env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + // env.close(); + // } + + // Testing A1 nft offer sell when A2 frozen by currency holder + { + auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + env(trust(A2, G1["USD"](0), tfSetFreeze)); + env.close(); + + // test: A2 cannot send USD for NFT + env(token::acceptSellOffer(A2, sellOfferIndex)); + env.close(); + + env(trust(A2, G1["USD"](0), tfClearFreeze)); + env.close(); + } + + // // Testing A1 nft offer sell when A2 deep frozen by currency holder + if (features[featureDeepFreeze]) + { + auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // test: A2 cannot send USD for NFT + env(token::acceptSellOffer(A2, sellOfferIndex), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + + env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } + } + // Helper function to extract trustline flags from open ledger uint32_t getTrustlineFlags( @@ -1811,6 +1958,26 @@ class Freeze_test : public beast::unit_test::suite return keylet::check(account, uSequence).key; } + uint256 + createNFTOffer( + test::jtx::Env& env, + test::jtx::Account const& account, + test::jtx::PrettyAmount const& currency) + { + using namespace test::jtx; + uint256 const nftID{token::getNextID(env, account, 0u, tfTransferable)}; + env(token::mint(account, 0), txflags(tfTransferable)); + env.close(); + + uint256 const sellOfferIndex = + keylet::nftoffer(account, env.seq(account)).key; + env(token::createOffer(account, nftID, currency), + txflags(tfSellNFToken)); + env.close(); + + return sellOfferIndex; + } + public: void run() override @@ -1828,6 +1995,7 @@ class Freeze_test : public beast::unit_test::suite testChecksWhenFrozen(features); testAMMWhenFreeze(features); testLongerPathsWhenFrozen(features); + testNFTOffersWhenFreeze(features); }; using namespace test::jtx; auto const sa = supported_amendments(); From 0d8855ae872166a7f51ade3d6389694c5316f4fb Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 17 Jan 2025 16:34:16 +0000 Subject: [PATCH 26/37] Fixed flag --- src/xrpld/app/tx/detail/SetTrust.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index b4c7d90b10f..b1e0494ba46 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -87,7 +87,7 @@ SetTrust::preflight(PreflightContext const& ctx) // `tfTrustSetMask`, they are not valid if the amendment is not enabled. if (uTxFlags & (tfSetDeepFreeze | tfClearDeepFreeze)) { - return temDISABLED; + return temINVALID_FLAG; } } From a73ea014d1a2072ae7d0117692a3d3fa23361e3c Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 17 Jan 2025 16:55:58 +0000 Subject: [PATCH 27/37] Small fixes --- include/xrpl/protocol/LedgerFormats.h | 4 ++-- include/xrpl/protocol/detail/features.macro | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 776ebc59ff6..5f3cca53ac8 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -162,10 +162,10 @@ enum LedgerSpecificFlags { lsfHighNoRipple = 0x00200000, lsfLowFreeze = 0x00400000, // True, low side has set freeze flag lsfHighFreeze = 0x00800000, // True, high side has set freeze flag - lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client - // apps to identify payments via AMM. lsfLowDeepFreeze = 0x02000000, // True, low side has set deep freeze flag lsfHighDeepFreeze = 0x04000000, // True, high side has set deep freeze flag + lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client + // apps to identify payments via AMM. // ltSIGNER_LIST lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 8771823f03d..322670c5170 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -28,7 +28,6 @@ // Keep it sorted in reverse chronological order. // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. -// clang-format off XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) @@ -119,4 +118,3 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete) -// clang-format on \ No newline at end of file From 411a0f06326a3ceefb0915e8618d396f481a49cc Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 17 Jan 2025 17:08:47 +0000 Subject: [PATCH 28/37] Unity build fix --- src/test/app/Freeze_test.cpp | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 721e25c2bb9..2ce722a5567 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1035,7 +1035,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1043,7 +1043,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1061,7 +1061,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1069,7 +1069,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1087,14 +1087,14 @@ class Freeze_test : public beast::unit_test::suite // test: A1 can send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: G1 can send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1112,7 +1112,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1120,7 +1120,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1145,14 +1145,14 @@ class Freeze_test : public beast::unit_test::suite // test: A1 can send XRP using USD through A2 offer env(pay(A1, G1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); // test: G1 can send XRP using USD through A2 offer env(pay(G1, A1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1170,7 +1170,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send XRP using USD through A2 offer env(pay(A1, G1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1178,7 +1178,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send XRP using USD through A2 offer env(pay(G1, A1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1198,7 +1198,7 @@ class Freeze_test : public beast::unit_test::suite // TODO: This might be not a correct behavior. A2 doesn't want to // receive USDs. env(pay(A1, G1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1207,7 +1207,7 @@ class Freeze_test : public beast::unit_test::suite // TODO: This might be not a correct behavior. A2 doesn't want to // receive USDs. env(pay(G1, A1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1225,7 +1225,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send XRP using USD through A2 offer env(pay(A1, G1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1233,7 +1233,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send XRP using USD through A2 offer env(pay(G1, A1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1705,14 +1705,14 @@ class Freeze_test : public beast::unit_test::suite // test: can still use XRP to make payment env(pay(A1, A2, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: cannot use USD to make payment env(pay(A1, A2, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_DRY)); @@ -1720,14 +1720,14 @@ class Freeze_test : public beast::unit_test::suite // test: can still receive USD payments. env(pay(A2, A1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: can still receive XRP payments. env(pay(A2, A1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1745,14 +1745,14 @@ class Freeze_test : public beast::unit_test::suite // test: can still use XRP to make payment env(pay(A1, A2, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: cannot use USD to make payment env(pay(A1, A2, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_DRY)); @@ -1760,7 +1760,7 @@ class Freeze_test : public beast::unit_test::suite // test: cannot receive USD payments. env(pay(A2, A1, USD(10)), - path(~USD), + test::jtx::path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_DRY)); @@ -1768,7 +1768,7 @@ class Freeze_test : public beast::unit_test::suite // test: can still receive XRP payments. env(pay(A2, A1, XRP(10)), - path(~XRP), + test::jtx::path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); From 4c5c9ccf5aebc7ed91f223707cb17d025062599e Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 17 Jan 2025 17:15:27 +0000 Subject: [PATCH 29/37] simplified unity build fix --- src/test/app/Freeze_test.cpp | 50 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 2ce722a5567..e933a694919 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1008,6 +1008,7 @@ class Freeze_test : public beast::unit_test::suite { testcase("Longer paths payment on frozen trust lines"); using namespace test::jtx; + using path = test::jtx::path; Env env(*this, features); Account G1{"G1"}; @@ -1035,7 +1036,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1043,7 +1044,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1061,7 +1062,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1069,7 +1070,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1087,14 +1088,14 @@ class Freeze_test : public beast::unit_test::suite // test: A1 can send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: G1 can send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1112,7 +1113,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send USD using XRP through A2 offer env(pay(A1, G1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1120,7 +1121,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send USD using XRP through A2 offer env(pay(G1, A1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1145,14 +1146,14 @@ class Freeze_test : public beast::unit_test::suite // test: A1 can send XRP using USD through A2 offer env(pay(A1, G1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); // test: G1 can send XRP using USD through A2 offer env(pay(G1, A1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1170,7 +1171,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send XRP using USD through A2 offer env(pay(A1, G1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1178,7 +1179,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send XRP using USD through A2 offer env(pay(G1, A1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1198,7 +1199,7 @@ class Freeze_test : public beast::unit_test::suite // TODO: This might be not a correct behavior. A2 doesn't want to // receive USDs. env(pay(A1, G1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1207,7 +1208,7 @@ class Freeze_test : public beast::unit_test::suite // TODO: This might be not a correct behavior. A2 doesn't want to // receive USDs. env(pay(G1, A1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1225,7 +1226,7 @@ class Freeze_test : public beast::unit_test::suite // test: A1 cannot send XRP using USD through A2 offer env(pay(A1, G1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1233,7 +1234,7 @@ class Freeze_test : public beast::unit_test::suite // test: G1 cannot send XRP using USD through A2 offer env(pay(G1, A1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_PARTIAL)); @@ -1678,6 +1679,7 @@ class Freeze_test : public beast::unit_test::suite { testcase("AMM payments on frozen trust lines"); using namespace test::jtx; + using path = test::jtx::path; Env env(*this, features); Account G1{"G1"}; @@ -1705,14 +1707,14 @@ class Freeze_test : public beast::unit_test::suite // test: can still use XRP to make payment env(pay(A1, A2, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: cannot use USD to make payment env(pay(A1, A2, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_DRY)); @@ -1720,14 +1722,14 @@ class Freeze_test : public beast::unit_test::suite // test: can still receive USD payments. env(pay(A2, A1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: can still receive XRP payments. env(pay(A2, A1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); @@ -1745,14 +1747,14 @@ class Freeze_test : public beast::unit_test::suite // test: can still use XRP to make payment env(pay(A1, A2, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect)); env.close(); // test: cannot use USD to make payment env(pay(A1, A2, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect), ter(tecPATH_DRY)); @@ -1760,7 +1762,7 @@ class Freeze_test : public beast::unit_test::suite // test: cannot receive USD payments. env(pay(A2, A1, USD(10)), - test::jtx::path(~USD), + path(~USD), sendmax(XRP(11)), txflags(tfNoRippleDirect), ter(tecPATH_DRY)); @@ -1768,7 +1770,7 @@ class Freeze_test : public beast::unit_test::suite // test: can still receive XRP payments. env(pay(A2, A1, XRP(10)), - test::jtx::path(~XRP), + path(~XRP), sendmax(USD(11)), txflags(tfNoRippleDirect)); env.close(); From 9f21f5dc49cc9323f826803ddd8f3514db319310 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Fri, 17 Jan 2025 17:35:14 +0000 Subject: [PATCH 30/37] Fixed tests --- src/test/app/Freeze_test.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index e933a694919..749394decee 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -261,10 +261,11 @@ class Freeze_test : public beast::unit_test::suite else { // test: applying deep freeze before amendment fails - env(trust(G1, A1["USD"](0), tfSetDeepFreeze), ter(temDISABLED)); + env(trust(G1, A1["USD"](0), tfSetDeepFreeze), ter(temINVALID_FLAG)); // test: clearing deep freeze before amendment fails - env(trust(G1, A1["USD"](0), tfClearDeepFreeze), ter(temDISABLED)); + env(trust(G1, A1["USD"](0), tfClearDeepFreeze), + ter(temINVALID_FLAG)); } } From 8c40ab969c92191fef69a1d0f450d86847153c9a Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 20 Jan 2025 14:27:07 +0000 Subject: [PATCH 31/37] Fixed NFT deep freeze offer acceptance --- src/test/app/Freeze_test.cpp | 49 +++++++------ .../app/tx/detail/NFTokenAcceptOffer.cpp | 68 +++++++++++++++++++ src/xrpld/app/tx/detail/NFTokenAcceptOffer.h | 8 +++ 3 files changed, 99 insertions(+), 26 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 749394decee..b909ae86baa 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1818,21 +1818,20 @@ class Freeze_test : public beast::unit_test::suite } // Testing A2 nft offer sell when A2 deep frozen by issuer - // if (features[featureDeepFreeze]) - // { - // auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + if (features[featureDeepFreeze]) + { + auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); - // env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - // env.close(); + env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); - // // test: A2 cannot receive USD for his NFT - // env(token::acceptSellOffer(A1, sellOfferIndex), - // ter(tecINSUFFICIENT_FUNDS)); - // env.close(); + // test: A2 cannot receive USD for his NFT + env(token::acceptSellOffer(A1, sellOfferIndex), ter(tecFROZEN)); + env.close(); - // env(trust(G1, A2["USD"](0), tfClearFreeze | tfClearDeepFreeze)); - // env.close(); - // } + env(trust(G1, A2["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } // Testing A1 nft offer sell when A2 frozen by issuer { @@ -1872,8 +1871,7 @@ class Freeze_test : public beast::unit_test::suite // env.close(); // // test: A2 cannot receive USD for his NFT - // env(token::acceptSellOffer(A1, sellOfferIndex), - // ter(tecINSUFFICIENT_FUNDS)); + // env(token::acceptSellOffer(A1, sellOfferIndex), ter(tecFROZEN)); // env.close(); // env(trust(A2, G1["USD"](0), tfClearFreeze)); @@ -1881,21 +1879,20 @@ class Freeze_test : public beast::unit_test::suite // } // // Testing A2 nft offer sell when A2 deep frozen by currency holder - // if (features[featureDeepFreeze]) - // { - // auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + if (features[featureDeepFreeze]) + { + auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); - // env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); - // env.close(); + env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env.close(); - // // test: A2 cannot receive USD for his NFT - // env(token::acceptSellOffer(A1, sellOfferIndex), - // ter(tecINSUFFICIENT_FUNDS)); - // env.close(); + // test: A2 cannot receive USD for his NFT + env(token::acceptSellOffer(A1, sellOfferIndex), ter(tecFROZEN)); + env.close(); - // env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); - // env.close(); - // } + env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env.close(); + } // Testing A1 nft offer sell when A2 frozen by currency holder { diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index b884a791e78..10af14c0a47 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -268,6 +268,21 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) ctx.j) < needed) return tecINSUFFICIENT_FUNDS; } + + // Make sure that we are allowed to hold what the taker will pay us. + // This is the same approach taken by usual offers except we don't check + // trust line authorization at the moment. + if (!needed.native()) + { + auto const result = checkAcceptAsset( + ctx.view, + ctx.flags, + (*so)[sfOwner], + ctx.j, + Issue(needed.getCurrency(), needed.getIssuer())); + if (result != tesSUCCESS) + return result; + } } // Fix a bug where the transfer of an NFToken with a transfer fee could @@ -510,4 +525,57 @@ NFTokenAcceptOffer::doApply() return tecINTERNAL; } +TER +NFTokenAcceptOffer::checkAcceptAsset( + ReadView const& view, + ApplyFlags const flags, + AccountID const id, + beast::Journal const j, + Issue const& issue) +{ + // Only valid for custom currencies + XRPL_ASSERT( + !isXRP(issue.currency), + "ripple::CreateOffer::checkAcceptAsset : input is not XRP"); + + auto const issuerAccount = view.read(keylet::account(issue.account)); + + if (!issuerAccount) + { + JLOG(j.debug()) + << "delay: can't receive IOUs from non-existent issuer: " + << to_string(issue.account); + + return (flags & tapRETRY) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER}; + } + + // An account can not create a trustline to itself, so no line can exist + // to be frozen. Additionally, an issuer can always accept its own + // issuance. + if (issue.account == id) + { + return tesSUCCESS; + } + + auto const trustLine = + view.read(keylet::line(id, issue.account, issue.currency)); + + if (!trustLine) + { + return tesSUCCESS; + } + + // There's no difference which side enacted deep freeze, accepting + // tokens shouldn't be possible. + bool const deepFrozen = + (*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze); + + if (deepFrozen) + { + return tecFROZEN; + } + + return tesSUCCESS; +} + } // namespace ripple diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h index dff3febbb21..6a594e2b2c8 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.h @@ -44,6 +44,14 @@ class NFTokenAcceptOffer : public Transactor AccountID const& seller, uint256 const& nfTokenID); + static TER + checkAcceptAsset( + ReadView const& view, + ApplyFlags const flags, + AccountID const id, + beast::Journal const j, + Issue const& issue); + public: static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; From c5aa145f876e4271206995f124ba6a2b2609d84f Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 20 Jan 2025 15:15:14 +0000 Subject: [PATCH 32/37] Added invariant check for deep freeze flag on a trust line --- src/test/ledger/Invariants_test.cpp | 47 ++++++++++++++++++++++ src/xrpld/app/tx/detail/InvariantCheck.cpp | 38 +++++++++++++++++ src/xrpld/app/tx/detail/InvariantCheck.h | 28 +++++++++++++ 3 files changed, 113 insertions(+) diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 993104d6ad8..3ce69d4403f 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -408,6 +408,52 @@ class Invariants_test : public beast::unit_test::suite }); } + void + testNoDeepFreezeTrustLinesWithoutFreeze() + { + using namespace test::jtx; + testcase << "trust lines with deep freeze flag without freeze " + "not allowed"; + doInvariantCheck( + {{"a trust line with deep freeze flag without normal freeze was " + "created"}}, + [](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const sleNew = std::make_shared( + keylet::line(A1, A2, A1["USD"].currency)); + std::uint32_t uFlags = 0u; + uFlags |= lsfLowDeepFreeze; + sleNew->setFieldU32(sfFlags, uFlags); + ac.view().insert(sleNew); + return true; + }); + + doInvariantCheck( + {{"a trust line with deep freeze flag without normal freeze was " + "created"}}, + [](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const sleNew = std::make_shared( + keylet::line(A1, A2, A1["USD"].currency)); + std::uint32_t uFlags = 0u; + uFlags |= lsfHighDeepFreeze; + sleNew->setFieldU32(sfFlags, uFlags); + ac.view().insert(sleNew); + return true; + }); + + doInvariantCheck( + {{"a trust line with deep freeze flag without normal freeze was " + "created"}}, + [](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const sleNew = std::make_shared( + keylet::line(A1, A2, A1["USD"].currency)); + std::uint32_t uFlags = 0u; + uFlags |= lsfLowDeepFreeze | lsfHighDeepFreeze; + sleNew->setFieldU32(sfFlags, uFlags); + ac.view().insert(sleNew); + return true; + }); + } + void testXRPBalanceCheck() { @@ -1061,6 +1107,7 @@ class Invariants_test : public beast::unit_test::suite testAccountRootsDeletedClean(); testTypesMatch(); testNoXRPTrustLine(); + testNoDeepFreezeTrustLinesWithoutFreeze(); testXRPBalanceCheck(); testTransactionFeeCheck(); testNoBadOffers(); diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 63794023d40..8870500fc2a 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -556,6 +556,44 @@ NoXRPTrustLines::finalize( //------------------------------------------------------------------------------ +void +NoDeepFreezeTrustLinesWithoutFreeze::visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltRIPPLE_STATE) + { + std::uint32_t const uFlags = after->getFieldU32(sfFlags); + bool const lowFreeze = uFlags & lsfLowFreeze; + bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze; + + bool const highFreeze = uFlags & lsfHighFreeze; + bool const highDeepFreeze = uFlags & lsfHighDeepFreeze; + + deepFreezeWithoutFreeze_ = + (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze); + } +} + +bool +NoDeepFreezeTrustLinesWithoutFreeze::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) +{ + if (!deepFreezeWithoutFreeze_) + return true; + + JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag " + "without normal freeze was created"; + return false; +} + +//------------------------------------------------------------------------------ + void ValidNewAccountRoot::visitEntry( bool, diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index 19c4ef3e23f..6fa4243e3d2 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -270,6 +270,33 @@ class NoXRPTrustLines beast::Journal const&); }; +/** + * @brief Invariant: Trust lines with deep freeze flag are not allowed if normal + * freeze flag is not set. + * + * We iterate all the trust lines created by this transaction and ensure + * that they don't have deep freeze flag set without normal freeze flag set. + */ +class NoDeepFreezeTrustLinesWithoutFreeze +{ + bool deepFreezeWithoutFreeze_ = false; + +public: + void + visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&); + + bool + finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const&); +}; + /** * @brief Invariant: offers should be for non-negative amounts and must not * be XRP to XRP. @@ -518,6 +545,7 @@ using InvariantChecks = std::tuple< XRPBalanceChecks, XRPNotCreated, NoXRPTrustLines, + NoDeepFreezeTrustLinesWithoutFreeze, NoBadOffers, NoZeroEscrow, ValidNewAccountRoot, From 851e619d624bd1ed8531fe38be33da2a1a727736 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 20 Jan 2025 15:42:01 +0000 Subject: [PATCH 33/37] Added deep freeze into acoount_lines rpc response --- include/xrpl/protocol/jss.h | 2 ++ src/test/rpc/AccountLines_test.cpp | 16 ++++++++++++++-- src/xrpld/app/paths/TrustLine.h | 14 ++++++++++++++ src/xrpld/rpc/handlers/AccountLines.cpp | 4 ++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index c41d7ef2594..d8916762556 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -282,6 +282,8 @@ JSS(flags); // out: AccountOffers, JSS(forward); // in: AccountTx JSS(freeze); // out: AccountLines JSS(freeze_peer); // out: AccountLines +JSS(deep_freeze); // out: AccountLines +JSS(deep_freeze_peer); // out: AccountLines JSS(frozen_balances); // out: GatewayBalances JSS(full); // in: LedgerClearer, handlers/Ledger JSS(full_reply); // out: PathFind diff --git a/src/test/rpc/AccountLines_test.cpp b/src/test/rpc/AccountLines_test.cpp index d104ea14b0a..bae00d7a769 100644 --- a/src/test/rpc/AccountLines_test.cpp +++ b/src/test/rpc/AccountLines_test.cpp @@ -167,7 +167,11 @@ class AccountLines_test : public beast::unit_test::suite env.close(); // Set flags on gw2 trust lines so we can look for them. - env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze)); + env(trust( + alice, + gw2Currency(0), + gw2, + tfSetNoRipple | tfSetFreeze | tfSetDeepFreeze)); } env.close(); LedgerInfo const ledger58Info = env.closed()->info(); @@ -344,6 +348,7 @@ class AccountLines_test : public beast::unit_test::suite gw2.human() + R"("})"); auto const& line = lines[jss::result][jss::lines][0u]; BEAST_EXPECT(line[jss::freeze].asBool() == true); + BEAST_EXPECT(line[jss::deep_freeze].asBool() == true); BEAST_EXPECT(line[jss::no_ripple].asBool() == true); BEAST_EXPECT(line[jss::peer_authorized].asBool() == true); } @@ -359,6 +364,7 @@ class AccountLines_test : public beast::unit_test::suite alice.human() + R"("})"); auto const& lineA = linesA[jss::result][jss::lines][0u]; BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true); + BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true); BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true); BEAST_EXPECT(lineA[jss::authorized].asBool() == true); @@ -981,7 +987,11 @@ class AccountLines_test : public beast::unit_test::suite env.close(); // Set flags on gw2 trust lines so we can look for them. - env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze)); + env(trust( + alice, + gw2Currency(0), + gw2, + tfSetNoRipple | tfSetFreeze | tfSetDeepFreeze)); } env.close(); LedgerInfo const ledger58Info = env.closed()->info(); @@ -1311,6 +1321,7 @@ class AccountLines_test : public beast::unit_test::suite gw2.human() + R"("}})"); auto const& line = lines[jss::result][jss::lines][0u]; BEAST_EXPECT(line[jss::freeze].asBool() == true); + BEAST_EXPECT(line[jss::deep_freeze].asBool() == true); BEAST_EXPECT(line[jss::no_ripple].asBool() == true); BEAST_EXPECT(line[jss::peer_authorized].asBool() == true); BEAST_EXPECT( @@ -1338,6 +1349,7 @@ class AccountLines_test : public beast::unit_test::suite alice.human() + R"("}})"); auto const& lineA = linesA[jss::result][jss::lines][0u]; BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true); + BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true); BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true); BEAST_EXPECT(lineA[jss::authorized].asBool() == true); BEAST_EXPECT( diff --git a/src/xrpld/app/paths/TrustLine.h b/src/xrpld/app/paths/TrustLine.h index 381ef471875..4189f7ff481 100644 --- a/src/xrpld/app/paths/TrustLine.h +++ b/src/xrpld/app/paths/TrustLine.h @@ -139,6 +139,13 @@ class TrustLineBase return mFlags & (mViewLowest ? lsfLowFreeze : lsfHighFreeze); } + /** Have we set the deep freeze flag on our peer */ + bool + getDeepFreeze() const + { + return mFlags & (mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze); + } + /** Has the peer set the freeze flag on us */ bool getFreezePeer() const @@ -146,6 +153,13 @@ class TrustLineBase return mFlags & (!mViewLowest ? lsfLowFreeze : lsfHighFreeze); } + /** Has the peer set the deep freeze flag on us */ + bool + getDeepFreezePeer() const + { + return mFlags & (!mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze); + } + STAmount const& getBalance() const { diff --git a/src/xrpld/rpc/handlers/AccountLines.cpp b/src/xrpld/rpc/handlers/AccountLines.cpp index e2e6ce19ded..5170342eb99 100644 --- a/src/xrpld/rpc/handlers/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/AccountLines.cpp @@ -62,6 +62,10 @@ addLine(Json::Value& jsonLines, RPCTrustLine const& line) jPeer[jss::freeze] = true; if (line.getFreezePeer()) jPeer[jss::freeze_peer] = true; + if (line.getDeepFreeze()) + jPeer[jss::deep_freeze] = true; + if (line.getDeepFreezePeer()) + jPeer[jss::deep_freeze_peer] = true; } // { From 290b9086f759bf60aa7fd65f5561ef8075eec4a2 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Mon, 20 Jan 2025 15:47:59 +0000 Subject: [PATCH 34/37] Uncommented NFT test with freeze on currency holder side --- src/test/app/Freeze_test.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index b909ae86baa..207a99ee599 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1864,19 +1864,20 @@ class Freeze_test : public beast::unit_test::suite env.close(); } - // // Testing A2 nft offer sell when A2 frozen by currency holder - // { - // auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); - // env(trust(A2, G1["USD"](0), tfSetFreeze)); - // env.close(); - - // // test: A2 cannot receive USD for his NFT - // env(token::acceptSellOffer(A1, sellOfferIndex), ter(tecFROZEN)); - // env.close(); - - // env(trust(A2, G1["USD"](0), tfClearFreeze)); - // env.close(); - // } + // Testing A2 nft offer sell when A2 frozen by currency holder + { + auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + env(trust(A2, G1["USD"](0), tfSetFreeze)); + env.close(); + + // test: this should actually fail since the currency holder doesn't + // want to receive this currency. + env(token::acceptSellOffer(A1, sellOfferIndex)); + env.close(); + + env(trust(A2, G1["USD"](0), tfClearFreeze)); + env.close(); + } // // Testing A2 nft offer sell when A2 deep frozen by currency holder if (features[featureDeepFreeze]) From 9db6765ede9b5d850b5a84ed787e4de81a113e62 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Tue, 21 Jan 2025 13:26:04 +0000 Subject: [PATCH 35/37] Added more invariant tests --- src/test/ledger/Invariants_test.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 3ce69d4403f..2658634e082 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -452,6 +452,32 @@ class Invariants_test : public beast::unit_test::suite ac.view().insert(sleNew); return true; }); + + doInvariantCheck( + {{"a trust line with deep freeze flag without normal freeze was " + "created"}}, + [](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const sleNew = std::make_shared( + keylet::line(A1, A2, A1["USD"].currency)); + std::uint32_t uFlags = 0u; + uFlags |= lsfLowDeepFreeze | lsfHighFreeze; + sleNew->setFieldU32(sfFlags, uFlags); + ac.view().insert(sleNew); + return true; + }); + + doInvariantCheck( + {{"a trust line with deep freeze flag without normal freeze was " + "created"}}, + [](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const sleNew = std::make_shared( + keylet::line(A1, A2, A1["USD"].currency)); + std::uint32_t uFlags = 0u; + uFlags |= lsfLowFreeze | lsfHighDeepFreeze; + sleNew->setFieldU32(sfFlags, uFlags); + ac.view().insert(sleNew); + return true; + }); } void From 7ba80fedfae2b21ff7dc6fadaf908b8b5a843f16 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Wed, 22 Jan 2025 17:34:59 +0000 Subject: [PATCH 36/37] Comment fixes --- src/test/app/Freeze_test.cpp | 18 +++++++++--------- src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 207a99ee599..dfab4ec233a 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1805,7 +1805,7 @@ class Freeze_test : public beast::unit_test::suite // Testing A2 nft offer sell when A2 frozen by issuer { - auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A2, USD(10)); env(trust(G1, A2["USD"](0), tfSetFreeze)); env.close(); @@ -1820,7 +1820,7 @@ class Freeze_test : public beast::unit_test::suite // Testing A2 nft offer sell when A2 deep frozen by issuer if (features[featureDeepFreeze]) { - auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A2, USD(10)); env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); env.close(); @@ -1835,7 +1835,7 @@ class Freeze_test : public beast::unit_test::suite // Testing A1 nft offer sell when A2 frozen by issuer { - auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A1, USD(10)); env(trust(G1, A2["USD"](0), tfSetFreeze)); env.close(); @@ -1851,7 +1851,7 @@ class Freeze_test : public beast::unit_test::suite // Testing A1 nft offer sell when A2 deep frozen by issuer if (features[featureDeepFreeze]) { - auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A1, USD(10)); env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze)); env.close(); @@ -1866,7 +1866,7 @@ class Freeze_test : public beast::unit_test::suite // Testing A2 nft offer sell when A2 frozen by currency holder { - auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A2, USD(10)); env(trust(A2, G1["USD"](0), tfSetFreeze)); env.close(); @@ -1882,7 +1882,7 @@ class Freeze_test : public beast::unit_test::suite // // Testing A2 nft offer sell when A2 deep frozen by currency holder if (features[featureDeepFreeze]) { - auto const sellOfferIndex = createNFTOffer(env, A2, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A2, USD(10)); env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); env.close(); @@ -1897,7 +1897,7 @@ class Freeze_test : public beast::unit_test::suite // Testing A1 nft offer sell when A2 frozen by currency holder { - auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A1, USD(10)); env(trust(A2, G1["USD"](0), tfSetFreeze)); env.close(); @@ -1912,7 +1912,7 @@ class Freeze_test : public beast::unit_test::suite // // Testing A1 nft offer sell when A2 deep frozen by currency holder if (features[featureDeepFreeze]) { - auto const sellOfferIndex = createNFTOffer(env, A1, USD(10)); + auto const sellOfferIndex = createNFTSellOffer(env, A1, USD(10)); env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); env.close(); @@ -1960,7 +1960,7 @@ class Freeze_test : public beast::unit_test::suite } uint256 - createNFTOffer( + createNFTSellOffer( test::jtx::Env& env, test::jtx::Account const& account, test::jtx::PrettyAmount const& currency) diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index 10af14c0a47..988a9d56b92 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -272,7 +272,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) // Make sure that we are allowed to hold what the taker will pay us. // This is the same approach taken by usual offers except we don't check // trust line authorization at the moment. - if (!needed.native()) + if (ctx.view.rules().enabled(featureDeepFreeze) && !needed.native()) { auto const result = checkAcceptAsset( ctx.view, @@ -536,7 +536,7 @@ NFTokenAcceptOffer::checkAcceptAsset( // Only valid for custom currencies XRPL_ASSERT( !isXRP(issue.currency), - "ripple::CreateOffer::checkAcceptAsset : input is not XRP"); + "NFTokenAcceptOffer::checkAcceptAsset : input is not XRP"); auto const issuerAccount = view.read(keylet::account(issue.account)); @@ -546,7 +546,7 @@ NFTokenAcceptOffer::checkAcceptAsset( << "delay: can't receive IOUs from non-existent issuer: " << to_string(issue.account); - return (flags & tapRETRY) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER}; + return tecNO_ISSUER; } // An account can not create a trustline to itself, so no line can exist From 79a4da300a957b87579d0e0bb5ee6c28609ef6d7 Mon Sep 17 00:00:00 2001 From: Vladislav Vysokikh Date: Thu, 23 Jan 2025 14:25:51 +0000 Subject: [PATCH 37/37] Comment fixes --- src/test/app/Freeze_test.cpp | 91 +++++++++++-------- .../app/tx/detail/NFTokenAcceptOffer.cpp | 2 +- 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index dfab4ec233a..2af55afb268 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -244,6 +244,17 @@ class Freeze_test : public beast::unit_test::suite env.close(); } + // test: Holder clearing freeze flags has no effect. Each sides' + // flags are independent + env(trust(A1, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + { + auto const flags = getTrustlineFlags(env, 2u, 1u); + BEAST_EXPECT(flags & lsfLowFreeze); + BEAST_EXPECT(flags & lsfLowDeepFreeze); + BEAST_EXPECT(!(flags & (lsfHighFreeze | lsfHighDeepFreeze))); + env.close(); + } + // test: Issuer can't clear normal freeze when line is deep frozen env(trust(G1, A1["USD"](0), tfClearFreeze), ter(tecNO_PERMISSION)); @@ -760,7 +771,8 @@ class Freeze_test : public beast::unit_test::suite env.fund(XRP(10000), G1, A1, A2, A3); env.close(); - env.trust(G1["USD"](10000), A1, A2, A3); + auto const limit = USD(10000); + env.trust(limit, A1, A2, A3); env.close(); env(pay(G1, A1, USD(1000))); @@ -924,12 +936,10 @@ class Freeze_test : public beast::unit_test::suite // Testing aggressive and passive offer placing, trustline frozen by // the holder { - env(trust(A1, G1["USD"](0), tfSetFreeze)); + env(trust(A1, limit, tfSetFreeze)); env.close(); // test: A1 can make passive buy offer - // TODO: I expected that it would not possible to create buy offer - // in this situation. Fix later. env(offer(A1, USD(1), XRP(0.5)), txflags(tfPassive)); env.close(); env.require(balance(A1, USD(1002)), offers(A1, 1)); @@ -972,7 +982,7 @@ class Freeze_test : public beast::unit_test::suite env.close(); env.require(balance(A1, USD(1001)), offers(A1, 0)); - env(trust(A1, G1["USD"](0), tfClearFreeze)); + env(trust(A1, limit, tfClearFreeze)); env.close(); } @@ -980,7 +990,7 @@ class Freeze_test : public beast::unit_test::suite // by the holder if (features[featureDeepFreeze]) { - env(trust(A1, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust(A1, limit, tfSetFreeze | tfSetDeepFreeze)); env.close(); // test: A1 cannot create passive buy offer @@ -999,7 +1009,7 @@ class Freeze_test : public beast::unit_test::suite env(offer(A1, XRP(1), USD(1)), ter(tecUNFUNDED_OFFER)); env.close(); - env(trust(A1, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(A1, limit, tfClearFreeze | tfClearDeepFreeze)); env.close(); } } @@ -1020,7 +1030,8 @@ class Freeze_test : public beast::unit_test::suite env.fund(XRP(10000), G1, A1, A2); env.close(); - env.trust(G1["USD"](10000), A1, A2); + auto const limit = USD(10000); + env.trust(limit, A1, A2); env.close(); env(pay(G1, A1, USD(1000))); @@ -1084,7 +1095,7 @@ class Freeze_test : public beast::unit_test::suite // Testing payments A1 <-> G1 using offer from A2 frozen by currency // holder. { - env(trust(A2, G1["USD"](0), tfSetFreeze)); + env(trust(A2, limit, tfSetFreeze)); env.close(); // test: A1 can send USD using XRP through A2 offer @@ -1101,7 +1112,7 @@ class Freeze_test : public beast::unit_test::suite txflags(tfNoRippleDirect)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze)); + env(trust(A2, limit, tfClearFreeze)); env.close(); } @@ -1109,7 +1120,7 @@ class Freeze_test : public beast::unit_test::suite // currency holder. if (features[featureDeepFreeze]) { - env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust(A2, limit, tfSetFreeze | tfSetDeepFreeze)); env.close(); // test: A1 cannot send USD using XRP through A2 offer @@ -1128,7 +1139,7 @@ class Freeze_test : public beast::unit_test::suite ter(tecPATH_PARTIAL)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(A2, limit, tfClearFreeze | tfClearDeepFreeze)); env.close(); } @@ -1193,7 +1204,7 @@ class Freeze_test : public beast::unit_test::suite // Testing payments A1 <-> G1 using offer from A2 frozen by currency // holder. { - env(trust(A2, G1["USD"](0), tfSetFreeze)); + env(trust(A2, limit, tfSetFreeze)); env.close(); // test: A1 can send XRP using USD through A2 offer @@ -1214,7 +1225,7 @@ class Freeze_test : public beast::unit_test::suite txflags(tfNoRippleDirect)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze)); + env(trust(A2, limit, tfClearFreeze)); env.close(); } @@ -1222,7 +1233,7 @@ class Freeze_test : public beast::unit_test::suite // currency holder. if (features[featureDeepFreeze]) { - env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust(A2, limit, tfSetFreeze | tfSetDeepFreeze)); env.close(); // test: A1 cannot send XRP using USD through A2 offer @@ -1241,7 +1252,7 @@ class Freeze_test : public beast::unit_test::suite ter(tecPATH_PARTIAL)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(A2, limit, tfClearFreeze | tfClearDeepFreeze)); env.close(); } @@ -1267,7 +1278,8 @@ class Freeze_test : public beast::unit_test::suite env.fund(XRP(10000), G1, A1, A2); env.close(); - env.trust(G1["USD"](1000), A1, A2); + auto const limit = USD(10000); + env.trust(limit, A1, A2); env.close(); env(pay(G1, A1, USD(1000))); @@ -1328,7 +1340,7 @@ class Freeze_test : public beast::unit_test::suite env.close(); // A1 freezes trust line - env(trust(A1, G1["USD"](0), tfSetFreeze)); + env(trust(A1, limit, tfSetFreeze)); env.close(); // Issuer and A2 must not be affected @@ -1343,15 +1355,15 @@ class Freeze_test : public beast::unit_test::suite env(pay(A1, A2, USD(1))); env.close(); - // Issuer cannot sent tokens to A1 - env(pay(G1, A1, USD(1)), ter(tecPATH_DRY)); + // Issuer can sent tokens to A1 + env(pay(G1, A1, USD(1))); // A2 cannot send tokens to A1 env(pay(A2, A1, USD(1)), ter(tecPATH_DRY)); if (features[featureDeepFreeze]) { // A1 deep freezes trust line - env(trust(A1, G1["USD"](0), tfSetDeepFreeze)); + env(trust(A1, limit, tfSetDeepFreeze)); env.close(); // Issuer and A2 must not be affected @@ -1363,8 +1375,8 @@ class Freeze_test : public beast::unit_test::suite env(pay(A1, G1, USD(1))); env.close(); - // Issuer cannot send tokens to A1 - env(pay(G1, A1, USD(1)), ter(tecPATH_DRY)); + // Issuer can send tokens to A1 + env(pay(G1, A1, USD(1))); // A2 cannot send tokens to A1 env(pay(A2, A1, USD(1)), ter(tecPATH_DRY)); // A1 cannot send tokens to A2 @@ -1388,7 +1400,8 @@ class Freeze_test : public beast::unit_test::suite env.fund(XRP(10000), G1, A1, A2); env.close(); - env.trust(USD(1000), A1, A2); + auto const limit = USD(10000); + env.trust(limit, A1, A2); env.close(); env(pay(G1, A1, USD(1000))); @@ -1530,8 +1543,7 @@ class Freeze_test : public beast::unit_test::suite uint256 const checkId{getCheckIndex(G1, env.seq(G1))}; env(check::create(G1, A1, USD(10))); env.close(); - // This might be a bug in current check implementation. - // Normal freeze should not prevent A1 balance from increasing. + env(check::cash(A1, checkId, USD(10)), ter(tecFROZEN)); env.close(); } @@ -1596,7 +1608,7 @@ class Freeze_test : public beast::unit_test::suite // Testing creation and cashing of checks on a trustline frozen by // a currency holder { - env(trust(A1, G1["USD"](0), tfSetFreeze)); + env(trust(A1, limit, tfSetFreeze)); env.close(); // test: issuer writes check to A1. @@ -1629,7 +1641,7 @@ class Freeze_test : public beast::unit_test::suite env.close(); } - env(trust(A1, G1["USD"](0), tfClearFreeze)); + env(trust(A1, limit, tfClearFreeze)); env.close(); } @@ -1637,7 +1649,7 @@ class Freeze_test : public beast::unit_test::suite // a currency holder if (features[featureDeepFreeze]) { - env(trust(A1, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust(A1, limit, tfSetFreeze | tfSetDeepFreeze)); env.close(); // test: issuer writes check to A1. @@ -1670,7 +1682,7 @@ class Freeze_test : public beast::unit_test::suite env.close(); } - env(trust(A1, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(A1, limit, tfClearFreeze | tfClearDeepFreeze)); env.close(); } } @@ -1796,7 +1808,8 @@ class Freeze_test : public beast::unit_test::suite env.fund(XRP(10000), G1, A1, A2); env.close(); - env.trust(G1["USD"](10000), A1, A2); + auto const limit = USD(10000); + env.trust(limit, A1, A2); env.close(); env(pay(G1, A1, USD(1000))); @@ -1867,7 +1880,7 @@ class Freeze_test : public beast::unit_test::suite // Testing A2 nft offer sell when A2 frozen by currency holder { auto const sellOfferIndex = createNFTSellOffer(env, A2, USD(10)); - env(trust(A2, G1["USD"](0), tfSetFreeze)); + env(trust(A2, limit, tfSetFreeze)); env.close(); // test: this should actually fail since the currency holder doesn't @@ -1875,7 +1888,7 @@ class Freeze_test : public beast::unit_test::suite env(token::acceptSellOffer(A1, sellOfferIndex)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze)); + env(trust(A2, limit, tfClearFreeze)); env.close(); } @@ -1884,28 +1897,28 @@ class Freeze_test : public beast::unit_test::suite { auto const sellOfferIndex = createNFTSellOffer(env, A2, USD(10)); - env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust(A2, limit, tfSetFreeze | tfSetDeepFreeze)); env.close(); // test: A2 cannot receive USD for his NFT env(token::acceptSellOffer(A1, sellOfferIndex), ter(tecFROZEN)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(A2, limit, tfClearFreeze | tfClearDeepFreeze)); env.close(); } // Testing A1 nft offer sell when A2 frozen by currency holder { auto const sellOfferIndex = createNFTSellOffer(env, A1, USD(10)); - env(trust(A2, G1["USD"](0), tfSetFreeze)); + env(trust(A2, limit, tfSetFreeze)); env.close(); // test: A2 cannot send USD for NFT env(token::acceptSellOffer(A2, sellOfferIndex)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze)); + env(trust(A2, limit, tfClearFreeze)); env.close(); } @@ -1913,7 +1926,7 @@ class Freeze_test : public beast::unit_test::suite if (features[featureDeepFreeze]) { auto const sellOfferIndex = createNFTSellOffer(env, A1, USD(10)); - env(trust(A2, G1["USD"](0), tfSetFreeze | tfSetDeepFreeze)); + env(trust(A2, limit, tfSetFreeze | tfSetDeepFreeze)); env.close(); // test: A2 cannot send USD for NFT @@ -1921,7 +1934,7 @@ class Freeze_test : public beast::unit_test::suite ter(tecINSUFFICIENT_FUNDS)); env.close(); - env(trust(A2, G1["USD"](0), tfClearFreeze | tfClearDeepFreeze)); + env(trust(A2, limit, tfClearFreeze | tfClearDeepFreeze)); env.close(); } } diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index 988a9d56b92..2005fb282e7 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -279,7 +279,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) ctx.flags, (*so)[sfOwner], ctx.j, - Issue(needed.getCurrency(), needed.getIssuer())); + needed.asset().get()); if (result != tesSUCCESS) return result; }