diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 61a9c917a..78e34ec35 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 = 104; +static constexpr std::size_t numFeatures = 105; /** 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 e8de8f1ac..1300006c4 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -147,9 +147,8 @@ enum LedgerSpecificFlags { 0x40000000, // True, has minted tokens in the past lsfDisallowIncomingRemit = // True, no remits allowed to this account 0x80000000, - // 0x0004000 is available - lsfAllowTrustLineClawback = - 0x00008000, // True, enable clawback + lsfAllowTrustLineClawback = + 0x00001000, // True, enable clawback // ltOFFER lsfPassive = 0x00010000, @@ -164,10 +163,10 @@ enum LedgerSpecificFlags { lsfHighNoRipple = 0x00200000, lsfLowFreeze = 0x00400000, // True, low side has set freeze flag lsfHighFreeze = 0x00800000, // True, high side has set freeze flag - 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. + lsfLowDeepFreeze = 0x02000000, // 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/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 572ece7c6..04803f782 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -29,7 +29,6 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. -XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(Credentials, Supported::no, VoteBehavior::DefaultNo) @@ -51,6 +50,8 @@ XRPL_FEATURE(DID, Supported::no, VoteBehavior::DefaultNo XRPL_FIX (DisallowIncomingV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (ProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes) diff --git a/src/test/app/Clawback_test.cpp b/src/test/app/Clawback_test.cpp index c6623eaf7..39a78cb9c 100644 --- a/src/test/app/Clawback_test.cpp +++ b/src/test/app/Clawback_test.cpp @@ -823,6 +823,19 @@ class Clawback_test : public beast::unit_test::suite BEAST_EXPECT(getLineFreezeFlag(env, alice, bob, USD.currency)); } + static STAmount + lockedAmount( + test::jtx::Env const& env, + test::jtx::Account const& account, + test::jtx::Account const& gw, + test::jtx::IOU const& iou) + { + auto const sle = env.le(keylet::line(account, gw, iou.currency)); + if (sle->isFieldPresent(sfLockedBalance)) + return (*sle)[sfLockedBalance]; + return STAmount(iou, 0); + } + void testAmountExceedsAvailable(FeatureBitset features) { diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 06deed6fd..778e0bed7 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -4096,7 +4096,8 @@ struct Escrow_test : public beast::unit_test::suite env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze)); env.close(); - // bob cancel escrow fails because of deep frozen assets + // bob cancel escrow succeeds despite deep frozen assets (unlocking + // return is allowed) env(cancel(bob, alice, seq1), fee(baseFee), ter(tesSUCCESS)); env.close(); } @@ -4267,6 +4268,54 @@ struct Escrow_test : public beast::unit_test::suite } } + void + testIOUClawback(FeatureBitset features) + { + testcase("IOU Clawback"); + using namespace test::jtx; + using namespace std::chrono; + + Env env(*this, features); + Account alice{"alice"}; + Account bob{"bob"}; + Account gw{"gw"}; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + + auto const USD = gw["USD"]; + + // gw sets asfAllowTrustLineClawback + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + bool const clawbackEnabled = features[featureClawback]; + if (clawbackEnabled) + { + env.require(flags(gw, asfAllowTrustLineClawback)); + } + else + { + env.require(nflags(gw, asfAllowTrustLineClawback)); + } + + // gw issues 1000 USD to alice + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == USD(1000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(0)); + + // alice escrows token fails; cannot escrow clawable tokens + auto const createResult = + clawbackEnabled ? ter(tecNO_PERMISSION) : ter(tesSUCCESS); + env(escrow(alice, bob, USD(10)), + finish_time(env.now() + 1s), + createResult); + env.close(); + } + static uint256 getEscrowIndex(AccountID const& account, std::uint32_t uSequence) { @@ -4679,6 +4728,7 @@ struct Escrow_test : public beast::unit_test::suite testIOUTLFreeze(features); testIOUTLINSF(features); testIOUPrecisionLoss(features); + testIOUClawback(features); } public: @@ -4689,6 +4739,7 @@ public: FeatureBitset const all{supported_amendments() | featureCredentials}; testWithFeats(all - featurePaychanAndEscrowForTokens); testWithFeats(all); + testIOUWithFeats(all - featureClawback); testIOUWithFeats(all); testEscrowID(all); testCredentials(all); diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 9cd560919..2677c5a62 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -5576,6 +5576,22 @@ struct PayChan_test : public beast::unit_test::suite ter(tecFROZEN)); env.close(); + // clear freeze on alice trustline + env(trust( + gw, USD(100000), alice, tfClearFreeze | tfClearDeepFreeze)); + env.close(); + + // alice close paychan success + env(paychan::claim(alice, chan, reqBal, authAmt), + txflags(tfClose), + ter(tesSUCCESS)); + env.close(); + + // create paychan success + chan = channel(alice, bob, env.seq(alice)); + env(paychan::create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + // clear freeze on bob trustline env(trust(gw, USD(100000), bob, tfClearFreeze | tfClearDeepFreeze)); // clear freeze on alice trustline @@ -5815,6 +5831,53 @@ struct PayChan_test : public beast::unit_test::suite } } + void + testIOUClawback(FeatureBitset features) + { + testcase("IOU Clawback"); + using namespace test::jtx; + using namespace std::chrono; + + Env env(*this, features); + Account alice{"alice"}; + Account bob{"bob"}; + Account gw{"gw"}; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + + auto const USD = gw["USD"]; + + // gw sets asfAllowTrustLineClawback + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + bool const clawbackEnabled = features[featureClawback]; + if (clawbackEnabled) + { + env.require(flags(gw, asfAllowTrustLineClawback)); + } + else + { + env.require(nflags(gw, asfAllowTrustLineClawback)); + } + + // gw issues 1000 USD to alice + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == USD(1000)); + BEAST_EXPECT(env.balance(bob, USD) == USD(0)); + + // alice paychan token fails; cannot escrow clawable tokens + auto const createResult = + clawbackEnabled ? ter(tecNO_PERMISSION) : ter(tesSUCCESS); + env(paychan::create(alice, bob, USD(10), 100s, alice.pk()), + ter(createResult)); + env.close(); + } + void testWithFeats(FeatureBitset features) { @@ -5872,6 +5935,7 @@ struct PayChan_test : public beast::unit_test::suite testIOUTLINSF(features); testIOUMismatchFunding(features); testIOUPrecisionLoss(features); + testIOUClawback(features); } public: @@ -5885,6 +5949,7 @@ public: all - disallowIncoming - featurePaychanAndEscrowForTokens); testWithFeats(all); testIOUWithFeats(all - disallowIncoming); + testIOUWithFeats(all - featureClawback); testIOUWithFeats(all); testDepositAuthCreds(all); } diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 32420a3a2..1afc7226e 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -844,7 +844,7 @@ private: // Check // | otxn | tsh | cancel | create | cash | // | A | A | S | S | N/A | - // | A | D | N | S | N/A | + // | A | D | W | S | N/A | // | D | D | S | N/A | S | // | D | A | S | N/A | S | static uint256 @@ -898,7 +898,7 @@ private: // otxn: account // tsh destination - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -906,6 +906,9 @@ private: network::makeNetworkConfig(21337, "10", "1000000", "200000"), features}; + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + auto const account = Account("alice"); auto const dest = Account("bob"); env.fund(XRP(1000), account, dest); @@ -928,7 +931,14 @@ private: env.close(); // verify tsh hook triggered - testTSHStrongWeak(env, tshNONE, __LINE__); + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } } // otxn: dest @@ -1156,6 +1166,60 @@ private: // verify tsh hook triggered testTSHStrongWeak(env, tshSTRONG, __LINE__); } + + // otxn: dest + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // create check + uint256 const checkId{getCheckIndex(account, env.seq(account))}; + env(check::create(account, dest, USD(100)), ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // cash check + env(check::cash(dest, checkId, USD(100)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } // ClaimReward @@ -1238,6 +1302,90 @@ private: } } + void + testClawbackTSH(FeatureBitset features) + { + testcase("clawback tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: IOU issuer + // tsh issuer + // w/s: strong + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const holder = Account("bob"); + env.fund(XRP(1000), issuer, holder); + env.close(); + + env(fset(issuer, asfAllowTrustLineClawback)); + env.close(); + + env.trust(issuer["USD"](1000), holder); + env(pay(issuer, holder, issuer["USD"](1000))); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, issuer); + + // set tsh hook + setTSHHook(env, issuer, testStrong); + + // clawback + env(claw(issuer, holder["USD"](1000)), fee(XRP(1))); + env.close(); + + // verify tsh hook triggered + testTSHStrongWeak(env, tshSTRONG, __LINE__); + } + + // otxn: IOU issuer + // tsh holder + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const issuer = Account("gw"); + auto const holder = Account("bob"); + env.fund(XRP(1000), issuer, holder); + env.close(); + + env(fset(issuer, asfAllowTrustLineClawback)); + env.close(); + + env.trust(issuer["USD"](1000), holder); + env(pay(issuer, holder, issuer["USD"](1000))); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, holder); + + // set tsh hook + setTSHHook(env, holder, testStrong); + + // clawback + env(claw(issuer, holder["USD"](1000)), fee(XRP(1))); + env.close(); + + // verify tsh hook triggered + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } + } + // DepositPreauth // | otxn | tsh | preauth | // | A | A | S | @@ -1384,7 +1532,7 @@ private: // otxn: account // tsh dest - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -1392,6 +1540,9 @@ private: network::makeNetworkConfig(21337, "10", "1000000", "200000"), features}; + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + auto const account = Account("alice"); auto const dest = Account("bob"); env.fund(XRP(1000), account, dest); @@ -1421,7 +1572,14 @@ private: env.close(); // verify tsh hook triggered - testTSHStrongWeak(env, tshNONE, __LINE__); + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } } // otxn: dest @@ -1507,6 +1665,65 @@ private: // verify tsh hook triggered testTSHStrongWeak(env, tshSTRONG, __LINE__); } + + // otxn: account + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow(account, dest, USD(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // cancel escrow + env(cancel(account, account, seq1), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } void @@ -1571,7 +1788,7 @@ private: // otxn: account // tsh dest - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -1579,6 +1796,9 @@ private: network::makeNetworkConfig(21337, "10", "1000000", "200000"), features}; + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + auto const account = Account("alice"); auto const dest = Account("bob"); env.fund(XRP(1000), account, dest); @@ -1617,7 +1837,14 @@ private: env.close(); // verify tsh hook triggered - testTSHStrongWeak(env, tshNONE, __LINE__); + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } } // otxn: dest @@ -1725,6 +1952,74 @@ private: : (testStrong ? tshNONE : tshNONE)); testTSHStrongWeak(env, expected, __LINE__); } + + // otxn: account + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow(account, dest, USD(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // cancel escrow + Json::Value tx; + if (!env.current()->rules().enabled(fixXahauV1)) + { + tx = cancel(account, account, 0); + } + else + { + tx = cancel(account, account); + } + env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } void @@ -1809,6 +2104,60 @@ private: // verify tsh hook triggered testTSHStrongWeak(env, tshSTRONG, __LINE__); } + + // otxn: account + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // create escrow + NetClock::time_point const finishTime = env.now() + 1s; + NetClock::time_point const cancelTime = env.now() + 2s; + auto createTx = escrow(account, dest, USD(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + createTx[sfCancelAfter.jsonName] = + cancelTime.time_since_epoch().count(); + env(createTx, fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } void @@ -1975,6 +2324,62 @@ private: // verify tsh hook triggered testTSHStrongWeak(env, tshSTRONG, __LINE__); } + + // otxn: account + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // create escrow + auto const seq1 = env.seq(account); + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow(account, dest, USD(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // finish escrow + env(finish(account, account, seq1), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } void @@ -2185,6 +2590,72 @@ private: : (testStrong ? tshNONE : tshNONE)); testTSHStrongWeak(env, expected, __LINE__); } + + // otxn: dest + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // create escrow + uint256 const escrowId{getEscrowIndex(account, env.seq(account))}; + NetClock::time_point const finishTime = env.now() + 1s; + auto createTx = escrow(account, dest, USD(10)); + createTx[sfFinishAfter.jsonName] = + finishTime.time_since_epoch().count(); + env(createTx, ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // finish escrow + bool const fixV1 = env.current()->rules().enabled(fixXahauV1); + Json::Value tx; + if (!fixV1) + { + tx = finish(dest, account, 0); + } + else + { + tx = finish(dest, account); + } + env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } // GenesisMint @@ -2658,7 +3129,7 @@ private: // otxn: account // tsh cross - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -2699,6 +3170,51 @@ private: auto const expected = testStrong ? tshNONE : tshWEAK; testTSHStrongWeak(env, expected, __LINE__); } + + // otxn: account + // tsh cross issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("carol"); + auto const cross = Account("bob"); + auto const gw = Account{"alice"}; + + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, cross, gw); + env.close(); + env.trust(USD(100000), account); + env.trust(USD(100000), cross); + env.close(); + env(pay(gw, cross, USD(10000))); + env.close(); + + // cross create offer + env(offer(cross, XRP(1000), USD(1000))); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // create offer + env(offer(account, USD(1000), XRP(1000)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } } // Payment @@ -2815,6 +3331,46 @@ private: auto const expected = testStrong ? tshNONE : tshWEAK; testTSHStrongWeak(env, expected, __LINE__); } + + // otxn: account + // tsh cross issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + auto const cross = Account("bob"); + auto const dest = Account("carol"); + auto const gw = Account{"gateway"}; + env.fund(XRP(1000), account, cross, dest); + env.close(); + + // setup rippling + auto const USDA = account["USD"]; + auto const USDB = cross["USD"]; + auto const USDC = dest["USD"]; + env.trust(USDA(10), cross); + env.trust(USDB(10), dest); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, cross); + + // set tsh hook + setTSHHook(env, cross, testStrong); + + // payment + env(pay(account, dest, USDB(10)), paths(USDA), fee(XRP(1))); + env.close(); + + // verify tsh hook triggered + auto const expected = testStrong ? tshNONE : tshWEAK; + testTSHStrongWeak(env, expected, __LINE__); + } } // PaymentChannel @@ -3040,6 +3596,68 @@ private: // verify tsh hook triggered testTSHStrongWeak(env, tshSTRONG, __LINE__); } + + // otxn: account + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, USD(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + auto const delta = USD(1); + auto const reqBal = delta; + auto const authAmt = reqBal + USD(1); + + // claim paychannel + env(paychan::claim(account, chan, reqBal, authAmt), + txflags(tfClose), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } void @@ -3117,6 +3735,57 @@ private: // verify tsh hook triggered testTSHStrongWeak(env, tshSTRONG, __LINE__); } + + // otxn: account + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + env(paychan::create(account, dest, USD(10), settleDelay, pk), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } void @@ -3207,6 +3876,63 @@ private: auto const expected = testStrong ? tshNONE : tshWEAK; testTSHStrongWeak(env, expected, __LINE__); } + + // otxn: account + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account{"bob"}; + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(10'000), account); + env.trust(USD(10'000), dest); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // create paychannel + auto const pk = account.pk(); + auto const settleDelay = 100s; + auto const chan = channel(account, dest, env.seq(account)); + env(paychan::create(account, dest, USD(10), settleDelay, pk), + ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // fund paychannel + env(paychan::fund(account, chan, USD(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } // SetHook @@ -3792,7 +4518,7 @@ private: // otxn: owner // flag: not burnable // tsh issuer - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -3835,9 +4561,14 @@ private: // verify tsh hook triggered bool const fixV1 = env.current()->rules().enabled(fixXahauV1); + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + auto const expected = - (fixV1 ? (testStrong ? tshNONE : tshNONE) - : (testStrong ? tshSTRONG : tshSTRONG)); + (fixV1 + ? (testStrong ? tshNONE + : (withIOUIssuerWeakTSH ? tshWEAK : tshNONE)) + : (testStrong ? tshSTRONG : tshSTRONG)); testTSHStrongWeak(env, expected, __LINE__); } @@ -3893,7 +4624,7 @@ private: // otxn: owner // flag: burnable // tsh issuer - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -3937,16 +4668,20 @@ private: // verify tsh hook triggered bool const fixV1 = env.current()->rules().enabled(fixXahauV1); + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); auto const expected = - (fixV1 ? (testStrong ? tshNONE : tshNONE) - : (testStrong ? tshSTRONG : tshSTRONG)); + (fixV1 + ? (testStrong ? tshNONE + : (withIOUIssuerWeakTSH ? tshWEAK : tshNONE)) + : (testStrong ? tshSTRONG : tshSTRONG)); testTSHStrongWeak(env, expected, __LINE__); } // otxn: issuer // flag: burnable // tsh owner - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -3990,9 +4725,14 @@ private: // verify tsh hook triggered bool const fixV1 = env.current()->rules().enabled(fixXahauV1); + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + auto const expected = - (fixV1 ? (testStrong ? tshNONE : tshNONE) - : (testStrong ? tshSTRONG : tshSTRONG)); + (fixV1 + ? (testStrong ? tshNONE + : (withIOUIssuerWeakTSH ? tshWEAK : tshNONE)) + : (testStrong ? tshSTRONG : tshSTRONG)); testTSHStrongWeak(env, expected, __LINE__); } @@ -4293,6 +5033,66 @@ private: // verify tsh hook triggered testTSHStrongWeak(env, tshSTRONG, __LINE__); } + + // otxn: buyer + // tsh amount issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const issuer = Account("alice"); + auto const buyer = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), issuer, buyer, gw); + env.close(); + env.trust(USD(10'000), buyer); + env.close(); + env(pay(gw, buyer, USD(10'000))); + env.close(); + + std::string const uri(2, '?'); + auto const tid = uritoken::tokenid(issuer, uri); + std::string const hexid{strHex(tid)}; + + // mint uritoken + env(uritoken::mint(issuer, uri), + uritoken::dest(buyer), + uritoken::amt(USD(1)), + ter(tesSUCCESS)); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // buy uritoken + env(uritoken::buy(buyer, hexid), + uritoken::amt(USD(1)), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } } void @@ -4362,7 +5162,7 @@ private: // otxn: owner // flag: not burnable // tsh buyer - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -4370,6 +5170,9 @@ private: network::makeNetworkConfig(21337, "10", "1000000", "200000"), features}; + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + auto const issuer = Account("alice"); auto const owner = Account("bob"); auto const buyer = Account("carol"); @@ -4412,13 +5215,20 @@ private: env.close(); // verify tsh hook triggered - testTSHStrongWeak(env, tshNONE, __LINE__); + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } } // otxn: owner // flag: burnable // tsh buyer - // w/s: none + // w/s: weak for (bool const testStrong : {true, false}) { test::jtx::Env env{ @@ -4426,6 +5236,9 @@ private: network::makeNetworkConfig(21337, "10", "1000000", "200000"), features}; + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + auto const issuer = Account("alice"); auto const owner = Account("bob"); auto const buyer = Account("carol"); @@ -4469,7 +5282,14 @@ private: env.close(); // verify tsh hook triggered - testTSHStrongWeak(env, tshNONE, __LINE__); + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } } // otxn: owner @@ -5246,6 +6066,54 @@ private: testTSHStrongWeak(env, expected, __LINE__); } + // otxn: account + // tsh iou issuer + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + bool const withIOUIssuerWeakTSH = + env.current()->rules().enabled(featureIOUIssuerWeakTSH); + + auto const account = Account("alice"); + auto const dest = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, dest, gw); + env.close(); + env.trust(USD(100'000), account); + env.close(); + env(pay(gw, account, USD(10'000))); + env.close(); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, gw); + + // set tsh hook + setTSHHook(env, gw, testStrong); + + // payment + env(remit::remit(account, dest), + remit::amts({USD(100)}), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + if (withIOUIssuerWeakTSH && !testStrong) + { + testTSHStrongWeak(env, tshWEAK, __LINE__); + } + else + { + testTSHStrongWeak(env, tshNONE, __LINE__); + } + } + /* sfURITokenIDs */ @@ -5481,6 +6349,7 @@ private: testCheckCashTSH(features); testCheckCreateTSH(features); testClaimRewardTSH(features); + testClawbackTSH(features); testDepositPreauthTSH(features); testEscrowCancelTSH(features); testEscrowIDCancelTSH(features); @@ -5524,10 +6393,11 @@ public: using namespace test::jtx; static FeatureBitset const all{supported_amendments()}; - static std::array const feats{ + static std::array const feats{ all, - all - fixXahauV1 - fixXahauV2, - all - fixXahauV2, + all - fixXahauV1 - fixXahauV2 - featureIOUIssuerWeakTSH, + all - fixXahauV2 - featureIOUIssuerWeakTSH, + all - featureIOUIssuerWeakTSH, }; if (BEAST_EXPECT(instance < feats.size())) @@ -5555,12 +6425,15 @@ public: SetHookTSH0_test::run(i, last); \ } \ }; + SETHOOKTSH_TEST(1, false) SETHOOKTSH_TEST(2, false) +SETHOOKTSH_TEST(3, true) BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH0, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH1, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH2, app, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH3, app, ripple, 2); } // namespace test } // namespace ripple diff --git a/src/test/app/URIToken_test.cpp b/src/test/app/URIToken_test.cpp index 5a65fe0bf..a4c84138f 100644 --- a/src/test/app/URIToken_test.cpp +++ b/src/test/app/URIToken_test.cpp @@ -1932,31 +1932,135 @@ struct URIToken_test : public beast::unit_test::suite env(pay(gw, bob, USD(1000))); env.close(); - // set freeze on alice trustline - env(trust(gw, USD(10000), bob, tfSetFreeze)); + { + // IOU bob(frozen) -> alice + + // set freeze on bob trustline + env(trust(gw, USD(10000), bob, tfSetFreeze)); + env.close(); + + // setup mint + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + std::string const hexid{strHex(tid)}; + env(uritoken::mint(alice, uri)); + env(uritoken::sell(alice, hexid), uritoken::amt(USD(10))); + env.close(); + + // buy uritoken fails - frozen trustline + env(uritoken::buy(bob, hexid), + uritoken::amt(USD(10)), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + + // clear freeze on bob trustline + env(trust(gw, USD(10000), bob, tfClearFreeze)); + env.close(); + + // buy uri success + env(uritoken::buy(bob, hexid), uritoken::amt(USD(10))); + env.close(); + } + { + // IOU alice -> bob(frozen) + + // set freeze on bob trustline + env(trust(gw, USD(10000), bob, tfSetFreeze)); + env.close(); + + // setup mint + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(bob, uri); + std::string const hexid{strHex(tid)}; + env(uritoken::mint(bob, uri)); + env(uritoken::sell(bob, hexid), uritoken::amt(USD(10))); + env.close(); + + // buy uritoken fails - frozen trustline + env(uritoken::buy(alice, hexid), + uritoken::amt(USD(10)), + ter(tecFROZEN)); + env.close(); + + // clear freeze on bob trustline + env(trust(gw, USD(10000), bob, tfClearFreeze)); + env.close(); + + // buy uri success + env(uritoken::buy(alice, hexid), uritoken::amt(USD(10))); + env.close(); + } + } + // test DeepFreeze + { + // Env Setup + Env env{*this, features}; + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(1000))); + env(pay(gw, bob, USD(1000))); env.close(); - // setup mint - std::string const uri(maxTokenURILength, '?'); - auto const tid = uritoken::tokenid(alice, uri); - std::string const hexid{strHex(tid)}; - env(uritoken::mint(alice, uri)); - env(uritoken::sell(alice, hexid), uritoken::amt(USD(10))); - env.close(); + { + // set freeze on bob trustline + env(trust(gw, USD(10000), bob, tfSetFreeze | tfSetDeepFreeze)); + env.close(); - // buy uritoken fails - frozen trustline - env(uritoken::buy(bob, hexid), - uritoken::amt(USD(10)), - ter(tecINSUFFICIENT_FUNDS)); - env.close(); + // IOU bob(frozen) -> alice + // setup mint + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + std::string const hexid{strHex(tid)}; + env(uritoken::mint(alice, uri)); + env(uritoken::sell(alice, hexid), uritoken::amt(USD(10))); + env.close(); - // clear freeze on alice trustline - env(trust(gw, USD(10000), bob, tfClearFreeze)); - env.close(); + // buy uritoken fails - frozen trustline + env(uritoken::buy(bob, hexid), + uritoken::amt(USD(10)), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); - // buy uri success - env(uritoken::buy(bob, hexid), uritoken::amt(USD(10))); - env.close(); + // clear freeze on bob trustline + env(trust( + gw, USD(10000), bob, tfClearFreeze | tfClearDeepFreeze)); + env.close(); + + // buy uri success + env(uritoken::buy(bob, hexid), uritoken::amt(USD(10))); + env.close(); + } + { + // set freeze on bob trustline + env(trust(gw, USD(10000), bob, tfSetFreeze | tfSetDeepFreeze)); + env.close(); + + // IOU alice -> bob(frozen) + // setup mint + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(bob, uri); + std::string const hexid{strHex(tid)}; + env(uritoken::mint(bob, uri)); + env(uritoken::sell(bob, hexid), uritoken::amt(USD(10))); + env.close(); + + // buy uritoken fails - frozen trustline + env(uritoken::buy(alice, hexid), + uritoken::amt(USD(10)), + ter(tecFROZEN)); + env.close(); + + // clear freeze on bob trustline + env(trust( + gw, USD(10000), bob, tfClearFreeze | tfClearDeepFreeze)); + env.close(); + + // buy uri success + env(uritoken::buy(alice, hexid), uritoken::amt(USD(10))); + env.close(); + } } } diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 9146c157c..dd60cca04 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -115,12 +115,14 @@ class Invariants_test : public beast::unit_test::suite sink.messages().str().starts_with("Invariant failed:") || sink.messages().str().starts_with( "Transaction caused an exception")); - // uncomment if you want to log the invariant failure message - // log << " --> " << sink.messages().str() << std::endl; for (auto const& m : expect_logs) { - BEAST_EXPECT( - sink.messages().str().find(m) != std::string::npos); + if (sink.messages().str().find(m) == std::string::npos) + { + // uncomment if you want to log the invariant failure + // message log << " --> " << m << std::endl; + fail(); + } } } } diff --git a/src/xrpld/app/hook/detail/applyHook.cpp b/src/xrpld/app/hook/detail/applyHook.cpp index 18d64cc39..ea40cc3b7 100644 --- a/src/xrpld/app/hook/detail/applyHook.cpp +++ b/src/xrpld/app/hook/detail/applyHook.cpp @@ -342,8 +342,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) case ttOFFER_CANCEL: case ttTICKET_CREATE: case ttHOOK_SET: - case ttOFFER_CREATE: // this is handled seperately - { + case ttOFFER_CREATE: { break; } @@ -496,6 +495,12 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) break; } + case ttCLAWBACK: { + auto const amount = tx.getFieldAmount(sfAmount); + ADD_TSH(amount.getIssuer(), tshWEAK); + break; + } + default: return {}; } diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 1dbd69535..76f356a76 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -1314,7 +1314,8 @@ CreateOffer::doApply() if (result.second) { sb.apply(ctx_.rawView()); - addWeakTSHFromSandbox(sb); + if (!view().rules().enabled(featureIOUIssuerWeakTSH)) + addWeakTSHFromBalanceChanges(sb); } else sbCancel.apply(ctx_.rawView()); diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index d4d2ff652..9d9314241 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -253,7 +253,8 @@ EscrowCreate::doApply() ctx_.view(), {account, ctx_.tx[sfDestination]}, amount.issue(), - ctx_.journal); + ctx_.journal, + lhLOCKING); JLOG(ctx_.journal.trace()) << "EscrowCreate::doApply trustTransferAllowed result=" << result; diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index e01ab3491..735d1b35b 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -276,7 +276,8 @@ PayChanCreate::preclaim(PreclaimContext const& ctx) // between these accounts for this asset { TER const result = trustTransferAllowed( - ctx.view, {account, dst}, amount.issue(), ctx.j); + ctx.view, {account, dst}, amount.issue(), ctx.j, lhLOCKING); + JLOG(ctx.j.trace()) << "PayChanCreate::preclaim trustTransferAllowed result=" << result; diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index b94a8cc20..6090cf6f7 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -444,7 +444,8 @@ Payment::doApply() // on the TER. But always applying *should* // be safe. pv.apply(ctx_.rawView()); - addWeakTSHFromSandbox(pv); + if (!view().rules().enabled(featureIOUIssuerWeakTSH)) + addWeakTSHFromBalanceChanges(pv); } // TODO: is this right? If the amount is the correct amount, was diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index a2ddc0dae..f0b5fc3e3 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -600,8 +600,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: Insufficient reserve to add trust line."; // Another transaction could provide XRP to the account and then // this transaction would succeed. diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 4dac1ac34..630845bd6 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1579,15 +1579,15 @@ Transactor::doHookCallback( } void -Transactor::addWeakTSHFromSandbox(detail::ApplyViewBase const& pv) +Transactor::addWeakTSHFromBalanceChanges(detail::ApplyViewBase const& pv) { // If Hooks are enabled then non-issuers who have their TL balance - // modified by the execution of the path have the opportunity to have their - // weak hooks executed. + // modified by the execution of the transaction have the opportunity to have + // their weak hooks executed. if (ctx_.view().rules().enabled(featureHooks)) { - // anyone whose balance changed as a result of this Pathing is a weak - // TSH + // anyone whose balance changed as a result of transaction processing is + // a weak TSH auto bc = pv.balanceChanges(view()); for (auto const& entry : bc) @@ -1608,15 +1608,13 @@ Transactor::addWeakTSHFromSandbox(detail::ApplyViewBase const& pv) TER Transactor::doTSH( bool strong, // only strong iff true, only weak iff false + std::vector> tsh, hook::HookStateMap& stateMap, std::vector& results, std::shared_ptr const& provisionalMeta) { auto& view = ctx_.view(); - std::vector> tsh = - hook::getTransactionalStakeHolders(ctx_.tx, view); - // add the extra TSH marked out by the specific transactor (if applicable) if (!strong) for (auto& weakTsh : additionalWeakTSH_) @@ -1899,6 +1897,9 @@ Transactor::operator()() // application to the ledger std::map> aawMap; + std::vector> tsh = + hook::getTransactionalStakeHolders(ctx_.tx, ctx_.view()); + // Pre-application (Strong TSH) Hooks are executed here // These TSH have the right to rollback. // Weak TSH and callback are executed post-application. @@ -1927,7 +1928,7 @@ Transactor::operator()() // (who have the right to rollback the txn), any weak TSH will be // executed after doApply has been successful (callback as well) - result = doTSH(true, stateMap, hookResults, {}); + result = doTSH(true, tsh, stateMap, hookResults, {}); } // write state if all chains executed successfully @@ -2207,7 +2208,23 @@ Transactor::operator()() hook::HookStateMap stateMap; std::vector weakResults; - doTSH(false, stateMap, weakResults, proMeta); + if (view().rules().enabled(featureIOUIssuerWeakTSH)) + { + // Regardless of the transaction type, if the result changes the + // trust line balance, add high and low accounts to weakTSH. + ApplyViewImpl& avi = dynamic_cast(ctx_.view()); + addWeakTSHFromBalanceChanges(avi); + } + + if (!view().rules().enabled(featureIOUIssuerWeakTSH)) + { + // before amendment enabled, we need to get TSHs after txn basic + // processing If the object is deleted in cancen txn, it may not + // be possible to obtain the appropriate TSH. + tsh = hook::getTransactionalStakeHolders(ctx_.tx, ctx_.view()); + } + + doTSH(false, tsh, stateMap, weakResults, proMeta); // execute any hooks that nominated for 'again as weak' for (auto const& [accID, hookHashes] : aawMap) diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index a510c212e..e2f4891aa 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -188,6 +188,7 @@ protected: TER doTSH( bool strong, // only do strong TSH iff true, otheriwse only weak + std::vector> tsh, hook::HookStateMap& stateMap, std::vector& result, std::shared_ptr const& provisionalMeta); @@ -213,7 +214,7 @@ protected: std::shared_ptr const& provisionalMeta); void - addWeakTSHFromSandbox(detail::ApplyViewBase const& pv); + addWeakTSHFromBalanceChanges(detail::ApplyViewBase const& pv); // hooks amendment fields, these are unpopulated and unused unless // featureHooks is enabled diff --git a/src/xrpld/app/tx/detail/URIToken.cpp b/src/xrpld/app/tx/detail/URIToken.cpp index 0f0c4ffd0..452fe8429 100644 --- a/src/xrpld/app/tx/detail/URIToken.cpp +++ b/src/xrpld/app/tx/detail/URIToken.cpp @@ -837,7 +837,7 @@ URIToken::doApply() false, // authorize account (sleOwner->getFlags() & lsfDefaultRipple) == 0, false, // freeze trust line - false, // deep freeze trust line + false, // deepfreeze trust line *dstAmt, // initial balance zero Issue( purchaseAmount.getCurrency(), diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index 1ff6f69d0..367256ebb 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -82,6 +82,9 @@ hasExpired(ReadView const& view, std::optional const& exp); /** Controls the treatment of frozen account balances */ enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN }; +/** Controls the treatment of locked balances */ +enum LockHandling { lhLOCKING, lhUNLOCKING_RETURN, lhUNLOCKING_FORWARD }; + /** Controls the treatment of unauthorized MPT balances */ enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED }; @@ -643,8 +646,12 @@ trustAdjustLockedBalance( // check for freezes & auth { - TER const result = - trustTransferAllowed(view, parties, deltaAmt.issue(), j); + TER const result = trustTransferAllowed( + view, + parties, + deltaAmt.issue(), + j, + deltaLockCount == 1 ? lhLOCKING : lhUNLOCKING_RETURN); JLOG(j.trace()) << "trustAdjustLockedBalance: trustTransferAllowed result=" @@ -747,7 +754,8 @@ trustTransferAllowed( V& view, std::vector const& parties, Issue const& issue, - beast::Journal const& j) + beast::Journal const& j, + LockHandling lockHandling = lhUNLOCKING_FORWARD) { static_assert( std::is_same::value || @@ -776,6 +784,12 @@ trustTransferAllowed( uint32_t issuerFlags = sleIssuerAcc->getFieldU32(sfFlags); + // reject the creation of a locked balance (lhLOCKING) if the + // issuer has enabled clawback + if (lockHandling == lhLOCKING && view.rules().enabled(featureClawback) && + issuerFlags & lsfAllowTrustLineClawback) + return tecNO_PERMISSION; + bool requireAuth = issuerFlags & lsfRequireAuth; for (AccountID const& p : parties) @@ -783,6 +797,16 @@ trustTransferAllowed( if (p == issue.account) continue; + if (lockHandling != lhUNLOCKING_RETURN && + isDeepFrozen(view, p, issue.currency, issue.account)) + { + JLOG(j.trace()) << "trustTransferAllowed: " + // << "parties=[" << parties << "], " + << "issuer: " << issue.account << " " + << "has deep freeze on party: " << p; + return tecFROZEN; + } + auto const line = view.read(keylet::line(p, issue.account, issue.currency)); if (!line) diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 33034cc24..3d3b201ce 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -960,7 +960,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 + const 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.