From 12cafc87db9cceace413a5601970c6101246e427 Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:59:19 -0400 Subject: [PATCH 01/16] fix: some fixes on test logic, style and comments (#7668) --- src/test/app/Sponsor_test.cpp | 46 +++++++++++++++-------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 04d448e2bc..2432bbad8e 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -404,8 +404,6 @@ public: Account const alice("alice"); Account const bob("bob"); Account const sponsor("sponsor"); - Account const invalid("invalid"); - Account const signer1("signer1"); Account const signer2("signer2"); @@ -682,7 +680,7 @@ public: Account const sponsor("sponsor"); { - // both pre-funded and co-signed,pre-funded value is used + // Both pre-funded and co-signed; the pre-funded value is used. Env env{*this, testableAmendments()}; env.fund(XRP(10000), alice, bob, sponsor); env.close(); @@ -710,7 +708,7 @@ public: sle = env.le(keylet::sponsorship(sponsor, alice)); BEAST_EXPECT(sle); - BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99); // not paybacked + BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99); // not restored BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99)); } @@ -799,8 +797,7 @@ public: Account const alice("alice"); Account const bob("bob"); Account const sponsor1("sponsor1"); - Account const sponsor2("sponsor2"); - env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.fund(XRP(10000), alice, bob, sponsor1); env.close(); env(sponsor::transfer( @@ -1473,10 +1470,8 @@ public: env.close(); { - // Fee should be checked before permission check, - // otherwise tecNO_SPONSOR_PERMISSION returned when permission - // check fails could cause context reset to pay Fee because it - // is tec error + // Fee should be checked before sponsor permission, otherwise a tec + // result from a later check could cause context reset to pay Fee. auto aliceBalance = env.balance(alice); auto bobBalance = env.balance(bob); auto sponsorBalance = env.balance(sponsor); @@ -1553,8 +1548,6 @@ public: { // below reserve adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve); - env.close(); - auto const feeAmt = XRP(4); env(noop(alice), Fee(env.current()->fees().base), @@ -1588,10 +1581,8 @@ public: }; { - // Fee should be checked before permission check, - // otherwise tecNO_SPONSOR_PERMISSION returned when permission - // check fails could cause context reset to pay Fee because it - // is tec error + // Fee should be checked before sponsor permission, otherwise a tec + // result from a later check could cause context reset to pay Fee. auto aliceBalance = env.balance(alice); auto bobBalance = env.balance(bob); auto sponsorBalance = env.balance(sponsor); @@ -2227,7 +2218,7 @@ public: env.fund(XRP(10000), alice, bob, sponsor, sponsor2); env.close(); - // CheckCreate -> Check = 0Cancel + // CheckCreate -> Check -> CheckCancel uint32_t seq = 0; testEachSponsorship( @@ -2293,7 +2284,7 @@ public: env.fund(XRP(10000), alice, bob, sponsor); env.close(); - // CheckCreate -> = 0 CheckCash + // CheckCreate -> CheckCash uint32_t seq2 = 0; testEachSponsorship( env, @@ -2334,7 +2325,7 @@ public: env(pay(gw, alice, usd(100))); env.close(); - // CheckCreat = 0e -> CheckCash + // CheckCreate -> CheckCash uint32_t seq2 = 0; testEachSponsorship( env, @@ -2476,8 +2467,7 @@ public: env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); env.close(); env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), - sponsor::As(sponsor2, spfSponsorReserve), - Sig(sfSponsorSignature, sponsor2)); + sponsor::As(sponsor2, spfSponsorReserve)); env.close(); } @@ -2485,6 +2475,13 @@ public: BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + if (!cosigning) + { + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsorshipSle); + if (sponsorshipSle) + BEAST_EXPECT(sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0); + } // DepositPreauthDelete env(deposit::unauth(alice, sponsor)); @@ -2828,14 +2825,11 @@ public: env(jv); env.close(); - // for free mptoken checks - // adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + // Create tickets so the sponsor is past free-tier reserve behavior. std::uint32_t const ticketSeq{env.seq(sponsor) + 1}; env(ticket::create(sponsor, 2)); env.close(); - // adjustAccountXRPBalance(env, sponsor, reserve(env, 3) - - // drops(1)); jv = {}; jv[sfTransactionType] = jss::MPTokenAuthorize; jv[sfAccount] = bob.human(); @@ -3649,7 +3643,7 @@ public: jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = ""; auto const seq = env.seq(alice); - // should fail BatchSigners does have signer for SponsorSignature + // should fail because BatchSigners does not have signer for SponsorSignature env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), batch::Inner(jt.jv, seq + 1), batch::Inner(ticket::create(alice, 1), seq + 2), From 5bb979b2e5a4bc7d7e3040d3d941499df18b7dc0 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 12:39:09 -0400 Subject: [PATCH 02/16] feat: Add checks to block reserve sponsorship on OOS txs (#7605) --- src/libxrpl/tx/Transactor.cpp | 41 ++++++ .../Sponsor/SponsorshipTransfer.cpp | 77 ++++------- src/test/app/Sponsor_test.cpp | 120 +++++++++++++++++- src/test/rpc/AccountObjects_test.cpp | 69 ++-------- 4 files changed, 194 insertions(+), 113 deletions(-) diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index cd92bc4eae..c38109bece 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -202,6 +203,46 @@ preflight1Sponsor(PreflightContext const& ctx, AccountID const& id) JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags"; return temINVALID_FLAG; } + + // Reserve sponsorship is only permitted for an explicit allow-list of + // transaction types, for v1. All other tx types reject spfSponsorReserve here. + if ((sponsorFlags & spfSponsorReserve) != 0u) + { + static std::unordered_set const kReserveSponsorAllowed = { + // Explicitly allow-listed for v1. + ttDELEGATE_SET, + ttDEPOSIT_PREAUTH, + ttPAYMENT, + ttSIGNER_LIST_SET, + ttCHECK_CANCEL, + ttCHECK_CASH, + ttCHECK_CREATE, + ttESCROW_CANCEL, + ttESCROW_CREATE, + ttESCROW_FINISH, + ttPAYCHAN_CLAIM, + ttPAYCHAN_CREATE, + ttPAYCHAN_FUND, + ttCLAWBACK, + ttMPTOKEN_AUTHORIZE, + ttMPTOKEN_ISSUANCE_CREATE, + ttMPTOKEN_ISSUANCE_DESTROY, + ttMPTOKEN_ISSUANCE_SET, + ttTRUST_SET, + ttCREDENTIAL_ACCEPT, + ttCREDENTIAL_CREATE, + ttCREDENTIAL_DELETE, + ttACCOUNT_SET, + ttREGULAR_KEY_SET, + ttSPONSORSHIP_TRANSFER, + }; + if (!kReserveSponsorAllowed.contains(ctx.tx.getTxnType())) + { + JLOG(ctx.j.debug()) + << "preflight1: spfSponsorReserve not allowed for this transaction type"; + return temINVALID_FLAG; + } + } } else { diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp index c4e9ac817f..0c98511a5c 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -123,29 +122,15 @@ getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account { switch (sle->getType()) { - case ltNFTOKEN_OFFER: - case ltORACLE: - case ltPERMISSIONED_DOMAIN: - case ltVAULT: - case ltLOAN_BROKER: - return sle->getAccountID(sfOwner); case ltCHECK: - case ltDID: - case ltTICKET: - case ltOFFER: - case ltXCHAIN_OWNED_CLAIM_ID: - case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: case ltESCROW: case ltPAYCHAN: case ltMPTOKEN: case ltDELEGATE: - case ltBRIDGE: case ltDEPOSIT_PREAUTH: return sle->getAccountID(sfAccount); case ltMPTOKEN_ISSUANCE: return sle->getAccountID(sfIssuer); - case ltLOAN: - return sle->getAccountID(sfBorrower); case ltSIGNER_LIST: { auto const signerList = view.read(keylet::signers(account)); if (!signerList) @@ -159,12 +144,6 @@ getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account return sle->getAccountID(sfSubject); return sle->getAccountID(sfIssuer); } - case ltNFTOKEN_PAGE: { - // the upper 20 bytes of the index of ltNFTokenPage are the Owner's - // AccountID - uint256 const& key = sle->key(); - return AccountID::fromVoid(key.data()); - } case ltRIPPLE_STATE: { if (sle->isFlag(lsfHighReserve)) { @@ -180,39 +159,12 @@ getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account } return std::nullopt; } - case ltACCOUNT_ROOT: { - // AccountRoot is not supported for object sponsorship - return std::nullopt; - } - case ltNEGATIVE_UNL: - case ltDIR_NODE: - case ltAMENDMENTS: - case ltLEDGER_HASHES: - case ltFEE_SETTINGS: - case ltAMM: - return std::nullopt; default: + UNREACHABLE("Object is not supported by sponsorship."); return std::nullopt; }; } -template -inline std::uint32_t -getLedgerEntryOwnerCount(T const& sle) -{ - switch (sle->getType()) - { - case ltORACLE: { - return OracleSet::calculateOracleReserve(sle->getFieldArray(sfPriceDataSeries).size()); - } - // Vaults require 2 owner counts (the vault and a pseudo-account) - case ltVAULT: - return 2; - default: - return 1; - } -}; - template inline SF_ACCOUNT const& getLedgerEntrySponsorField(T const& sle, AccountID const& owner) @@ -265,7 +217,30 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) if (!sle) return tecNO_ENTRY; - auto const ownerCountDelta = getLedgerEntryOwnerCount(sle); + // v1 scope: an object is only sponsorable via SponsorshipTransfer if + // its creating transaction type is itself permitted to set + // spfSponsorReserve (the allow-list in preflight1Sponsor). Otherwise + // an Oracle / Ticket / DID / etc. could be retroactively sponsored + // even though its creating tx cannot be, leaving downstream + // transactors with no path to maintain the sponsorship invariants. + switch (sle->getType()) + { + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + case ltMPTOKEN: + case ltMPTOKEN_ISSUANCE: + case ltCREDENTIAL: + case ltRIPPLE_STATE: + case ltSIGNER_LIST: + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + break; + default: + return tecNO_PERMISSION; + } + + std::uint32_t const ownerCountDelta = 1; auto const owner = getLedgerEntryOwner(ctx.view, sle, sponseeID); if (!owner || owner != sponseeID) @@ -450,7 +425,7 @@ SponsorshipTransfer::doApply() if (!ownerSle) return tefINTERNAL; // LCOV_EXCL_LINE - std::int64_t const ownerCountDelta = getLedgerEntryOwnerCount(objSle); + std::int64_t const ownerCountDelta = 1; auto const& sponsorField = getLedgerEntrySponsorField(objSle, *ownerID); diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 2432bbad8e..51fe35cd77 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,10 +23,12 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -677,6 +680,7 @@ public: using namespace test::jtx; Account const alice("alice"); Account const bob("bob"); + Account const charlie("charlie"); Account const sponsor("sponsor"); { @@ -715,10 +719,10 @@ public: { // if pre-funded value is not enough, error Env env{*this, testableAmendments()}; - env.fund(XRP(10000), alice, bob, sponsor); + env.fund(XRP(10000), alice, bob, charlie, sponsor); env.close(); - env(sponsor::set(sponsor, 0, 10, XRP(10), XRP(100)), + env(sponsor::set(sponsor, 0, 1, XRP(10), XRP(100)), sponsor::SponseeAcc(alice), Ter(tesSUCCESS)); env.close(); @@ -847,19 +851,21 @@ public: Env env{*this, testableAmendments()}; Account const alice("alice"); Account const bob("bob"); + Account const charlie("charlie"); Account const sponsor("sponsor"); env.fund(XRP(10000), alice, bob, sponsor); env.close(); { // sponsor object - env(did::set(alice), - did::Uri("uri"), + env.fund(XRP(1000), charlie); + env.close(); + env(deposit::auth(alice, charlie), sponsor::As(sponsor, spfSponsorReserve), Sig(sfSponsorSignature, sponsor)); env.close(); - auto const keylet = keylet::did(alice); + auto const keylet = keylet::depositPreauth(alice, charlie); env(sponsor::transfer(bob, tfSponsorshipEnd, keylet.key), sponsor::SponseeAcc(alice), Ter(tecNO_PERMISSION)); @@ -1451,6 +1457,73 @@ public: Ter(tecNO_PERMISSION)); } } + + { + // existing owner objects that are outside the v1 SponsorshipTransfer + // object allow-list + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + env.close(); + + auto const checkBlocked = [&](Account const& account, uint256 const& objectID) { + env(sponsor::transfer(account, tfSponsorshipCreate, objectID), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_PERMISSION)); + env.close(); + }; + + auto const ticketSeq = env.seq(alice); + env(ticket::create(alice, 1)); + env.close(); + auto const ticketID = keylet::TicketT()(alice, ticketSeq + 1).key; + BEAST_EXPECT(env.le(keylet::unchecked(ticketID))); + checkBlocked(alice, ticketID); + + env(did::setValid(alice)); + env.close(); + auto const didKeylet = keylet::did(alice.id()); + BEAST_EXPECT(env.le(didKeylet)); + checkBlocked(alice, didKeylet.key); + + env(token::mint(alice, 0u)); + env.close(); + auto const nftPageKeylet = keylet::nftpageMax(alice); + BEAST_EXPECT(env.le(nftPageKeylet)); + checkBlocked(alice, nftPageKeylet.key); + + Account const borrower("borrower"); + env.fund(XRP(1000000), borrower); + env.close(); + + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset}); + env(vaultTx); + env.close(); + + env(vault.deposit( + {.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(1000)})); + env.close(); + + auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); + env(loanBroker::set(alice, vaultKeylet.key), + loanBroker::kDebtMaximum(xrpAsset(1000).value()), + loanBroker::kManagementFeeRate(TenthBips16{0}), + loanBroker::kCoverRateMinimum(TenthBips32{0}), + loanBroker::kCoverRateLiquidation(TenthBips32{0})); + env.close(); + + auto const loanKeylet = keylet::loan(brokerKeylet.key, 1); + env(loan::set(borrower, brokerKeylet.key, xrpAsset(100).value()), + Sig(sfCounterpartySignature, alice), + Fee(env.current()->fees().base * 2)); + env.close(); + BEAST_EXPECT(env.le(loanKeylet)); + checkBlocked(borrower, loanKeylet.key); + } } void @@ -2041,7 +2114,6 @@ public: env.fund(XRP(10000), alice, bob, sponsor); env.close(); - // test Sufficient sponsor balance if (cosigning) { adjustAccountXRPBalance(env, sponsor, reserve(env, 1) - drops(1)); @@ -3728,6 +3800,40 @@ public: } } + // Verify that the central allow-list in preflight1Sponsor rejects + // spfSponsorReserve for transaction types that v1 does not permit. + void + testReserveSponsorGate() + { + testcase("Reserve sponsor allow-list gate"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::set(sponsor, 0, 10, XRP(10)), sponsor::SponseeAcc(alice)); + env.close(); + + auto checkBlocked = [&](json::Value const& jv) { + env(jv, + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(temINVALID_FLAG)); + }; + + checkBlocked(ticket::create(alice, 1)); + checkBlocked(offer(alice, XRP(100), bob["USD"](100))); + checkBlocked(did::setValid(alice)); + checkBlocked(token::mint(alice, 0u)); + checkBlocked(sponsor::set(alice, 0, 10, XRP(10))); + checkBlocked(acctdelete(alice, bob)); + checkBlocked(loan::set(alice, uint256(1), Number{1})); + } + void testSponsorReserve(bool cosigning) { @@ -3769,6 +3875,8 @@ protected: testDelegatePermission(); testBatch(); + + testReserveSponsorGate(); } void diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index d319b92950..c040c0a5f5 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -1535,86 +1535,43 @@ public: } } - // A Sponsorship object is visible to both sides, but its reserve side - // belongs only to sfOwner. + // A Sponsorship object is visible to both sides. { Env env(*this, testableAmendments()); Account const owner("owner"); Account const sponsee("sponsee"); - Account const sponsor("sponsor"); - env.fund(XRP(10000), owner, sponsee, sponsor); + env.fund(XRP(10000), owner, sponsee); env.close(); - env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(owner)); + env(sponsor::set(owner, 0, 100, XRP(100)), sponsor::SponseeAcc(sponsee)); env.close(); - env(sponsor::set(owner, 0, 100, XRP(100)), - sponsor::SponseeAcc(sponsee), - sponsor::As(sponsor, spfSponsorReserve), - Sig(sfSponsorSignature, sponsor)); - env.close(); - - auto const sponsorship = env.le(keylet::sponsorship(owner, sponsee)); - if (!BEAST_EXPECT(sponsorship)) + auto const sponsorshipKeylet = keylet::sponsorship(owner, sponsee); + if (!BEAST_EXPECT(env.le(sponsorshipKeylet))) return; - BEAST_EXPECT(sponsorship->isFieldPresent(sfSponsor)); { - auto const resp = acctObjsSponsored(env, owner.id(), true, jss::sponsorship); + auto const resp = acctObjsSponsored(env, owner.id(), false, jss::sponsorship); auto const& objs = resp[jss::result][jss::account_objects]; if (BEAST_EXPECT(objs.size() == 1)) BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship); } - { - auto const resp = acctObjsSponsored(env, sponsee.id(), true, jss::sponsorship); - auto const& objs = resp[jss::result][jss::account_objects]; - BEAST_EXPECT(objs.size() == 0); - } { auto const resp = acctObjsSponsored(env, sponsee.id(), false, jss::sponsorship); auto const& objs = resp[jss::result][jss::account_objects]; if (BEAST_EXPECT(objs.size() == 1)) BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship); } - } - - // NFT page sponsored filter - { - // Mint an NFT for bob (creates NFT page) - env(token::mint(bob, 0)); - env.close(); - - auto const nftPageKeylet = keylet::nftpageMax(bob); - if (!BEAST_EXPECT(env.le(nftPageKeylet))) - return; - - // Sponsor the NFT page - env(sponsor::transfer(bob, tfSponsorshipCreate, nftPageKeylet.key), - sponsor::As(sponsor1, spfSponsorReserve), - Sig(sfSponsorSignature, sponsor1)); - env.close(); - - // Verify NFT page has sponsor field - auto const nftPage = env.le(nftPageKeylet); - if (!BEAST_EXPECT(nftPage)) - return; - BEAST_EXPECT(nftPage->isFieldPresent(sfSponsor)); - - // sponsored=true should include the sponsored NFT page - // sponsored=false should NOT include the sponsored NFT page - for (auto const sponsored : {true, false}) { - auto const resp = acctObjsSponsored(env, bob.id(), sponsored); + auto const resp = acctObjsSponsored(env, owner.id(), true, jss::sponsorship); auto const& objs = resp[jss::result][jss::account_objects]; - bool foundNFTPage = false; - for (auto const& obj : objs) - { - if (obj[sfLedgerEntryType.jsonName] == jss::NFTokenPage && - obj.isMember(sfSponsor.jsonName)) - foundNFTPage = true; - } - BEAST_EXPECT(foundNFTPage == sponsored); + BEAST_EXPECT(objs.size() == 0); + } + { + auto const resp = acctObjsSponsored(env, sponsee.id(), true, jss::sponsorship); + auto const& objs = resp[jss::result][jss::account_objects]; + BEAST_EXPECT(objs.size() == 0); } } } From f9af579690769b31b82968e54445b248fa528ac9 Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Tue, 30 Jun 2026 09:41:44 -0700 Subject: [PATCH 03/16] Sponsor edge cases (#7558) --- cspell.config.yaml | 1 + src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 22 ++-- .../ledger/helpers/RippleStateHelpers.cpp | 4 +- .../tx/transactors/lending/LoanSet.cpp | 7 ++ src/test/app/Loan_test.cpp | 13 ++- src/test/app/Sponsor_test.cpp | 101 ++++++++++++++++++ 6 files changed, 138 insertions(+), 10 deletions(-) diff --git a/cspell.config.yaml b/cspell.config.yaml index b13e79779b..386e1367c3 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -319,6 +319,7 @@ words: - unserviced - unshareable - unshares + - unsponsored - unsquelch - unsquelched - unsquelching diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 8fbda7f2bb..c98f2c7cd5 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -35,6 +35,7 @@ #include #include #include +#include namespace xrpl { @@ -144,7 +145,7 @@ addEmptyHolding( if (accountID == mptIssue.getIssuer()) return tesSUCCESS; - return authorizeMPToken(view, tx, priorBalance, mptID, accountID, journal); + return authorizeMPToken(view, tx, priorBalance, mptID, accountID, journal, 0, std::nullopt); } [[nodiscard]] TER @@ -193,9 +194,14 @@ authorizeMPToken( // - add the new mptokenKey to the owner directory // - create the MPToken object for the holder - auto const sponsorSle = getTxReserveSponsor(view, tx); - if (!sponsorSle) - return sponsorSle.error(); // LCOV_EXCL_LINE + SLE::pointer sponsorSle; + if (account == tx[sfAccount]) + { + auto sle = getTxReserveSponsor(view, tx); + if (!sle) + return sle.error(); // LCOV_EXCL_LINE + sponsorSle = std::move(*sle); + } // The reserve that is required to create the MPToken. Note // that although the reserve increases with every item @@ -205,10 +211,10 @@ authorizeMPToken( // The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on // the tx — the sponsor must always cover the reserve (via balance or prefunded // budget), so this check always runs for sponsored transactions. - if (*sponsorSle || ownerCount(sleAcct, journal) >= 2) + if (sponsorSle || ownerCount(sleAcct, journal) >= 2) { if (auto const ret = checkInsufficientReserve( - view, tx, sleAcct, priorBalance, *sponsorSle, 1, 0, journal); + view, tx, sleAcct, priorBalance, sponsorSle, 1, 0, journal); !isTesSuccess(ret)) return ret; } @@ -235,8 +241,8 @@ authorizeMPToken( view.insert(mptoken); // Update owner count. - adjustOwnerCount(view, sleAcct, *sponsorSle, 1, journal); - addSponsorToLedgerEntry(mptoken, *sponsorSle); + adjustOwnerCount(view, sleAcct, sponsorSle, 1, journal); + addSponsorToLedgerEntry(mptoken, sponsorSle); return tesSUCCESS; } diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index c715894145..8f5b3f0563 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -665,7 +665,9 @@ addEmptyHolding( return tecDUPLICATE; SLE::pointer sponsorSle; - if (!isPseudoAccount(sleDst)) + + // A reserve sponsor only covers tx.Account's own objects. + if (!isPseudoAccount(sleDst) && accountID == tx[sfAccount]) { auto sle = getTxReserveSponsor(view, tx); if (!sle) diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 6899f2b650..3f38a4f4d3 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,12 @@ LoanSet::preflight(PreflightContext const& ctx) auto const& tx = ctx.tx; + if (tx.isFieldPresent(sfSponsorFlags) && isReserveSponsored(tx)) + { + JLOG(ctx.j.debug()) << "LoanSet: reserve sponsorship is not allowed."; + return temINVALID_FLAG; + } + // Special case for Batch inner transactions if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch) && !tx.isFieldPresent(sfCounterparty)) diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 47ef458518..423fe7b6cc 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -4436,11 +4437,12 @@ protected: Account const lender{"lender"}; Account const issuer{"issuer"}; Account const borrower{"borrower"}; + Account const sponsor{"sponsor"}; auto const iou = issuer["IOU"]; auto testWrapper = [&](auto&& test) { Env env(*this); - env.fund(XRP(1'000), lender, issuer, borrower); + env.fund(XRP(1'000), lender, issuer, borrower, sponsor); env(trust(lender, iou(10'000'000))); env(pay(issuer, lender, iou(5'000'000))); BrokerInfo const brokerInfo{createVaultAndBroker(env, issuer["IOU"], lender)}; @@ -4455,6 +4457,15 @@ protected: BrokerInfo const& brokerInfo, jtx::Fee const& loanSetFee, Number const& debtMaximumRequest) { + for (auto const sponsorFlags : {spfSponsorReserve, spfSponsorReserve | spfSponsorFee}) + { + env(set(borrower, brokerInfo.brokerID, debtMaximumRequest), + sponsor::As(sponsor, sponsorFlags), + Sig(sfCounterpartySignature, lender), + loanSetFee, + Ter(temINVALID_FLAG)); + } + // first temBAD_SIGNER: TODO // invalid grace period { diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 51fe35cd77..6b974c28a1 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -3088,6 +3088,105 @@ public: BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); } + void + testSponsoredTrustLineNoFreeReserve() + { + // An account with ownerCount < 2 may create its first trust lines even + // without meeting the reserve. In any case, the sponsor pays the full + // reserve in all cases, even for the sponsee's very first trust line. + testcase("Sponsored trust line gets no free-reserve exception"); + using namespace test::jtx; + + Account const issuer("issuer"); + Account const alice("alice"); + Account const sponsor("sponsor"); + + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), issuer, alice, sponsor); + env.close(); + + auto const usd = issuer["usd"]; + auto const lineKeylet = keylet::line(alice, issuer, usd.currency); + + // Sponsor funded for exactly its base reserve + adjustAccountXRPBalance(env, sponsor, reserve(env, 0)); + + // alice's ownerCount is 0, so an unsponsored first trust line would be + // free; but because it is sponsored, the reserve check is enforced + // against the sponsor, which is one increment short. + env(trust(alice, usd(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecNO_LINE_INSUF_RESERVE)); + env.close(); + + BEAST_EXPECT(!env.le(lineKeylet)); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // Give the sponsor has exactly one owner-reserve increment; the same + // sponsored first trust line now succeeds and the sponsor pays for it. + adjustAccountXRPBalance(env, sponsor, reserve(env, 1)); + + env(trust(alice, usd(100)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(lineKeylet)); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, alice) == 1); + } + + void + testCoSignReserveBoundedBySponsorshipBudget() + { + // sponsor co-signs, so a fee-only object (ReserveCount == 0) makes a co-signed + // reserve sponsorship fail -- with no fallback to the sponsor's balance. + testcase("Co-signed reserve sponsorship is bounded by Sponsorship budget"); + using namespace test::jtx; + + Env env{*this, testableAmendments()}; + Account const sponsor("sponsor"); + Account const sponsee("sponsee"); + env.fund(XRP(10000), sponsor, sponsee); + env.close(); + + // Prefund a FEE-only Sponsorship for the sponsee; ReserveCount + // defaults to 0. + env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(sponsee)); + env.close(); + BEAST_EXPECT(env.le(keylet::sponsorship(sponsor, sponsee))); + + // Sponsee creates a Check with the sponsor co-signing the reserve. The + // fee-only Sponsorship's has ReserveCount (0), so this fails + // with tecINSUFFICIENT_RESERVE + env(check::create(sponsee, sponsor, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 0); + + // Bumping the Sponsorship's ReserveCount budget makes the same + // co-signed reserve sponsorship succeed, the budget is what gates it. + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(sponsee)); + env.close(); + + env(check::create(sponsee, sponsor, XRP(1)), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 1); + } + void testTrustSet(bool cosigning) { @@ -3876,6 +3975,8 @@ protected: testDelegatePermission(); testBatch(); + testSponsoredTrustLineNoFreeReserve(); + testCoSignReserveBoundedBySponsorshipBudget(); testReserveSponsorGate(); } From ca205ea05bd2ae44b162f6065113e512680b1892 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 13:35:53 -0400 Subject: [PATCH 04/16] fix build issues --- src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 3 ++- src/libxrpl/ledger/helpers/RippleStateHelpers.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index e2ec5374a4..f2db6194de 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -158,6 +158,7 @@ authorizeMPToken( std::optional holderID) { auto& view = ctx.view; + auto const tx = ctx.tx; auto const sleAcct = view.peek(keylet::account(account)); if (!sleAcct) return tecINTERNAL; // LCOV_EXCL_LINE @@ -213,7 +214,7 @@ authorizeMPToken( if (sponsorSle || ownerCount(sleAcct, journal) >= 2) { if (auto const ret = checkInsufficientReserve( - view, ctx.tx, sleAcct, priorBalance, sponsorSle, 1, 0, journal); + view, tx, sleAcct, priorBalance, sponsorSle, 1, 0, journal); !isTesSuccess(ret)) return ret; } diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 9a8bb02647..295dc204af 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -640,6 +640,7 @@ addEmptyHolding( beast::Journal journal) { auto& view = ctx.view; + auto const tx = ctx.tx; // Every account can hold XRP. An issuer can issue directly. if (issue.native() || accountID == issue.getIssuer()) return tesSUCCESS; @@ -676,7 +677,7 @@ addEmptyHolding( // Can the account cover the trust line reserve ? if (auto const ret = - checkInsufficientReserve(view, ctx.tx, sleDst, priorBalance, sponsorSle, 1, 0, journal); + checkInsufficientReserve(view, tx, sleDst, priorBalance, sponsorSle, 1, 0, journal); !isTesSuccess(ret)) return tecNO_LINE_INSUF_RESERVE; From 69534b3ec612ae76cf5f12c82f647343ecea6f7b Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 13:49:19 -0400 Subject: [PATCH 05/16] Revert "fix clang-tidy" This reverts commit 4cbc0502a157102af891357ea949ea9efb3ad011. --- include/xrpl/ledger/View.h | 2 +- include/xrpl/ledger/helpers/EscrowHelpers.h | 6 +++--- include/xrpl/ledger/helpers/MPTokenHelpers.h | 8 ++++---- include/xrpl/ledger/helpers/RippleStateHelpers.h | 4 ++-- include/xrpl/ledger/helpers/SponsorHelpers.h | 2 +- include/xrpl/ledger/helpers/TokenHelpers.h | 4 ++-- include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h | 2 +- src/libxrpl/ledger/View.cpp | 2 +- src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 8 ++++---- src/libxrpl/ledger/helpers/RippleStateHelpers.cpp | 4 ++-- src/libxrpl/ledger/helpers/TokenHelpers.cpp | 4 ++-- .../tx/transactors/token/MPTokenIssuanceCreate.cpp | 2 +- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 95581b7527..968923b50b 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -201,7 +201,7 @@ canWithdraw(ReadView const& view, STTx const& tx); [[nodiscard]] TER doWithdraw( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index 11282fad74..c1f488c4b9 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -17,7 +17,7 @@ namespace xrpl { template TER escrowUnlockApplyHelper( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, @@ -31,7 +31,7 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, @@ -168,7 +168,7 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index e0958ad992..370efe5342 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -71,7 +71,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue); [[nodiscard]] TER authorizeMPToken( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -102,7 +102,7 @@ requireAuth( */ [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, @@ -188,7 +188,7 @@ canMPTTradeAndTransfer( [[nodiscard]] TER addEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -196,7 +196,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal); diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index 907455f522..c6f66543a9 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -229,7 +229,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc /// canAddHolding() in preflight with the same View and Asset [[nodiscard]] TER addEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -237,7 +237,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal); diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h index d85a82ff54..ee7121b9ae 100644 --- a/include/xrpl/ledger/helpers/SponsorHelpers.h +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -35,7 +35,7 @@ getTxReserveSponsorAccountID(STTx const& tx) } inline std::expected -getTxReserveSponsor(ApplyViewContext const& ctx) +getTxReserveSponsor(ApplyViewContext&& ctx) { auto const sponsorID = getTxReserveSponsorAccountID(ctx.tx); if (sponsorID) diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index e6a3bc17f8..e061d48aab 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -230,7 +230,7 @@ canAddHolding(ReadView const& view, Asset const& asset); [[nodiscard]] TER addEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -238,7 +238,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal); diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index 359e306c14..f1189fe530 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -63,7 +63,7 @@ public: beast::Journal const& j) override; static std::expected - create(ApplyViewContext const& ctx, beast::Journal journal, MPTCreateArgs const& args); + create(ApplyViewContext&& ctx, beast::Journal journal, MPTCreateArgs const& args); }; } // namespace xrpl diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 27d55e0eb3..78c8417695 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -432,7 +432,7 @@ canWithdraw(ReadView const& view, STTx const& tx) TER doWithdraw( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index f2db6194de..564fcdd3a9 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -126,7 +126,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue) [[nodiscard]] TER addEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -149,7 +149,7 @@ addEmptyHolding( [[nodiscard]] TER authorizeMPToken( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -286,7 +286,7 @@ authorizeMPToken( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal) @@ -418,7 +418,7 @@ requireAuth( [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, // for MPToken authorization diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 295dc204af..7854970cdc 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -633,7 +633,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc TER addEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -702,7 +702,7 @@ addEmptyHolding( TER removeEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal) diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 3cf7387e45..5448aa1fe6 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -473,7 +473,7 @@ canAddHolding(ReadView const& view, Asset const& asset) TER addEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -489,7 +489,7 @@ addEmptyHolding( TER removeEmptyHolding( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal) diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 9d57cb7980..3235638b75 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -104,7 +104,7 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) std::expected MPTokenIssuanceCreate::create( - ApplyViewContext const& ctx, + ApplyViewContext&& ctx, beast::Journal journal, MPTCreateArgs const& args) { From f5689990ed50644adffbd538291bb2935077d396 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 13:49:58 -0400 Subject: [PATCH 06/16] Revert "switch to &&" This reverts commit 8f470b750dd08cc04e788b1443bec28c2fb2cbd4. --- include/xrpl/ledger/View.h | 2 +- include/xrpl/ledger/helpers/EscrowHelpers.h | 10 +++++----- include/xrpl/ledger/helpers/MPTokenHelpers.h | 8 ++++---- include/xrpl/ledger/helpers/RippleStateHelpers.h | 4 ++-- include/xrpl/ledger/helpers/SponsorHelpers.h | 2 +- include/xrpl/ledger/helpers/TokenHelpers.h | 4 ++-- .../tx/transactors/token/MPTokenIssuanceCreate.h | 2 +- src/libxrpl/ledger/View.cpp | 7 +++---- src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 12 ++++++------ src/libxrpl/ledger/helpers/RippleStateHelpers.cpp | 6 +++--- src/libxrpl/ledger/helpers/TokenHelpers.cpp | 13 +++++-------- .../tx/transactors/Sponsor/SponsorshipSet.cpp | 4 +++- .../tx/transactors/account/SignerListSet.cpp | 3 ++- src/libxrpl/tx/transactors/check/CheckCash.cpp | 3 ++- src/libxrpl/tx/transactors/check/CheckCreate.cpp | 3 ++- src/libxrpl/tx/transactors/delegate/DelegateSet.cpp | 4 +++- src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp | 3 ++- src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp | 3 ++- src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp | 3 ++- .../transactors/lending/LoanBrokerCoverWithdraw.cpp | 10 +++------- .../tx/transactors/lending/LoanBrokerDelete.cpp | 4 +++- .../tx/transactors/lending/LoanBrokerSet.cpp | 4 +++- src/libxrpl/tx/transactors/lending/LoanPay.cpp | 3 ++- src/libxrpl/tx/transactors/lending/LoanSet.cpp | 9 +++------ .../tx/transactors/payment/DepositPreauth.cpp | 5 +++-- .../payment_channel/PaymentChannelCreate.cpp | 3 ++- .../payment_channel/PaymentChannelFund.cpp | 3 ++- .../tx/transactors/token/MPTokenAuthorize.cpp | 3 ++- .../tx/transactors/token/MPTokenIssuanceCreate.cpp | 7 ++++--- src/libxrpl/tx/transactors/token/TrustSet.cpp | 4 +++- src/libxrpl/tx/transactors/vault/VaultClawback.cpp | 3 ++- src/libxrpl/tx/transactors/vault/VaultCreate.cpp | 9 +++++---- src/libxrpl/tx/transactors/vault/VaultDelete.cpp | 7 ++++--- src/libxrpl/tx/transactors/vault/VaultDeposit.cpp | 7 ++++--- src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp | 13 ++++--------- 35 files changed, 100 insertions(+), 90 deletions(-) diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 968923b50b..6643ab2cb1 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -201,7 +201,7 @@ canWithdraw(ReadView const& view, STTx const& tx); [[nodiscard]] TER doWithdraw( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index c1f488c4b9..533daa0cf2 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -17,7 +17,7 @@ namespace xrpl { template TER escrowUnlockApplyHelper( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, @@ -31,7 +31,7 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, @@ -58,7 +58,7 @@ escrowUnlockApplyHelper( if (!view.exists(trustLineKey) && createAsset) { // Can the account cover the trust line's reserve? - auto const sponsorSle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx}); + auto const sponsorSle = getTxReserveSponsor(ctx); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE @@ -168,7 +168,7 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, @@ -188,7 +188,7 @@ escrowUnlockApplyHelper( auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver); if (!view.exists(mptKeylet) && createAsset && !receiverIssuer) { - auto const sponsorSle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx}); + auto const sponsorSle = getTxReserveSponsor(ctx); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index 370efe5342..cbdffdfc91 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -71,7 +71,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue); [[nodiscard]] TER authorizeMPToken( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -102,7 +102,7 @@ requireAuth( */ [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, @@ -188,7 +188,7 @@ canMPTTradeAndTransfer( [[nodiscard]] TER addEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -196,7 +196,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal); diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index c6f66543a9..3cd5b79eee 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -229,7 +229,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc /// canAddHolding() in preflight with the same View and Asset [[nodiscard]] TER addEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -237,7 +237,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal); diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h index ee7121b9ae..e1696c7a9b 100644 --- a/include/xrpl/ledger/helpers/SponsorHelpers.h +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -35,7 +35,7 @@ getTxReserveSponsorAccountID(STTx const& tx) } inline std::expected -getTxReserveSponsor(ApplyViewContext&& ctx) +getTxReserveSponsor(ApplyViewContext& ctx) { auto const sponsorID = getTxReserveSponsorAccountID(ctx.tx); if (sponsorID) diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index e061d48aab..70d4ecab58 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -230,7 +230,7 @@ canAddHolding(ReadView const& view, Asset const& asset); [[nodiscard]] TER addEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -238,7 +238,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal); diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index f1189fe530..0b3acb5d79 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -63,7 +63,7 @@ public: beast::Journal const& j) override; static std::expected - create(ApplyViewContext&& ctx, beast::Journal journal, MPTCreateArgs const& args); + create(ApplyViewContext& ctx, beast::Journal journal, MPTCreateArgs const& args); }; } // namespace xrpl diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 78c8417695..51b0fcdb4c 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -432,7 +432,7 @@ canWithdraw(ReadView const& view, STTx const& tx) TER doWithdraw( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, @@ -443,8 +443,7 @@ doWithdraw( // Create trust line or MPToken for the receiving account if (dstAcct == senderAcct) { - if (auto const ter = addEmptyHolding( - {.view = ctx.view, .tx = ctx.tx}, senderAcct, priorBalance, amount.asset(), j); + if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j); !isTesSuccess(ter) && ter != tecDUPLICATE) return ter; } @@ -470,7 +469,7 @@ doWithdraw( // LCOV_EXCL_STOP } - auto const sponsorSle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx}); + auto const sponsorSle = getTxReserveSponsor(ctx); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 564fcdd3a9..9acb15d7ae 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -126,7 +126,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue) [[nodiscard]] TER addEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -149,7 +149,7 @@ addEmptyHolding( [[nodiscard]] TER authorizeMPToken( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -286,7 +286,7 @@ authorizeMPToken( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal) @@ -309,7 +309,7 @@ removeEmptyHolding( return tecHAS_OBLIGATIONS; return authorizeMPToken( - {.view = ctx.view, .tx = ctx.tx}, + ctx, {}, // priorBalance mptID, accountID, @@ -418,7 +418,7 @@ requireAuth( [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, // for MPToken authorization @@ -500,7 +500,7 @@ enforceMPTokenAuthorization( maybeDomainID.has_value() && sleToken == nullptr, "xrpl::enforceMPTokenAuthorization : new MPToken for domain"); if (auto const err = authorizeMPToken( - {.view = ctx.view, .tx = ctx.tx}, + ctx, priorBalance, // priorBalance mptIssuanceID, // mptIssuanceID account, // account diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 7854970cdc..4058ca89cc 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -633,7 +633,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc TER addEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -669,7 +669,7 @@ addEmptyHolding( // A reserve sponsor only covers tx.Account's own objects. if (!isPseudoAccount(sleDst) && accountID == tx[sfAccount]) { - auto sle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx}); + auto sle = getTxReserveSponsor(ctx); if (!sle) return sle.error(); // LCOV_EXCL_LINE sponsorSle = std::move(*sle); @@ -702,7 +702,7 @@ addEmptyHolding( TER removeEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal) diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 5448aa1fe6..482f62d468 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -473,7 +473,7 @@ canAddHolding(ReadView const& view, Asset const& asset) TER addEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -481,15 +481,14 @@ addEmptyHolding( { return std::visit( [&](TIss const& issue) -> TER { - return addEmptyHolding( - {.view = ctx.view, .tx = ctx.tx}, accountID, priorBalance, issue, journal); + return addEmptyHolding(ctx, accountID, priorBalance, issue, journal); }, asset.value()); } TER removeEmptyHolding( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal) @@ -498,13 +497,11 @@ removeEmptyHolding( [&](TIss const& issue) -> TER { if constexpr (std::is_same_v) { - return removeEmptyHolding( - {.view = ctx.view, .tx = ctx.tx}, accountID, issue, journal); + return removeEmptyHolding(ctx, accountID, issue, journal); } else { - return removeEmptyHolding( - {.view = ctx.view, .tx = ctx.tx}, accountID, issue, journal); + return removeEmptyHolding(ctx, accountID, issue, journal); } }, asset.value()); diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp index e578ae7ac3..02ba06c29e 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp @@ -203,7 +203,9 @@ SponsorshipSet::doApply() auto const feeAmount = ctx_.tx[~sfFeeAmount]; auto const maxFee = ctx_.tx[~sfMaxFee]; auto const remainingOwnerCount = ctx_.tx[~sfRemainingOwnerCount]; - auto reserveSponsorAccSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + + auto applyViewContext = ctx_.getApplyViewContext(); + auto reserveSponsorAccSle = getTxReserveSponsor(applyViewContext); if (!reserveSponsorAccSle) return reserveSponsorAccSle.error(); // LCOV_EXCL_LINE diff --git a/src/libxrpl/tx/transactors/account/SignerListSet.cpp b/src/libxrpl/tx/transactors/account/SignerListSet.cpp index 8a24d42c8b..8903da47cc 100644 --- a/src/libxrpl/tx/transactors/account/SignerListSet.cpp +++ b/src/libxrpl/tx/transactors/account/SignerListSet.cpp @@ -322,7 +322,8 @@ SignerListSet::replaceSignerList() // We check the reserve against the starting balance because we want to // allow dipping into the reserve to pay fees. This behavior is consistent // with TicketCreate. - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + auto applyViewContext = ctx_.getApplyViewContext(); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index 42cc435a9b..257571e5d7 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -388,7 +388,8 @@ CheckCash::doApply() STAmount const flowDeliver{ optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)}; - auto const sponsorSle = getTxReserveSponsor({.view = psb, .tx = ctx_.tx}); + auto applyViewContext = ApplyViewContext({.view = psb, .tx = ctx_.tx}); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index d2281ec3ae..4f9120d3a6 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -195,7 +195,8 @@ CheckCreate::doApply() // A check counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + auto applyViewContext = ctx_.getApplyViewContext(); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index ac5bf89d7d..d8cbed39ad 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -94,7 +94,9 @@ DelegateSet::doApply() auto const& permissions = ctx_.tx.getFieldArray(sfPermissions); if (permissions.empty()) return tecINTERNAL; // LCOV_EXCL_LINE - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + + auto applyViewContext = ctx_.getApplyViewContext(); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 2b3fd7abdd..4ef470a6c1 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -178,10 +178,11 @@ EscrowCancel::doApply() auto const issuer = amount.getIssuer(); bool const createAsset = account == accountID_; + auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - ctx_.getApplyViewContext(), + applyViewContext, kParityRate, ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep, preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 202aefa98f..59f7ee2f12 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -436,7 +436,8 @@ EscrowCreate::doApply() STAmount const amount{ctx_.tx[sfAmount]}; auto const balance = sle->getFieldAmount(sfBalance).xrp(); - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + auto applyViewContext = ctx_.getApplyViewContext(); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 5ededceffe..6b22e1b020 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -362,10 +362,11 @@ EscrowFinish::doApply() : kParityRate; auto const issuer = amount.getIssuer(); bool const createAsset = destID == accountID_; + auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - ctx_.getApplyViewContext(), + applyViewContext, lockedRate, sled, preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 674e6d1994..7f0b282be0 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -197,14 +197,10 @@ LoanBrokerCoverWithdraw::doApply() view().update(broker); associateAsset(*broker, vaultAsset); + + auto applyViewContext = ctx_.getApplyViewContext(); return doWithdraw( - ctx_.getApplyViewContext(), - accountID_, - dstAcct, - brokerPseudoID, - preFeeBalance_, - amount, - j_); + applyViewContext, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_); } void diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index d597114ab3..40ae0ed802 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -158,7 +158,9 @@ LoanBrokerDelete::doApply() view(), brokerPseudoID, accountID_, coverAvailable, j_, {}, WaiveTransferFee::Yes)) return ter; } - if (auto ter = removeEmptyHolding(ctx_.getApplyViewContext(), brokerPseudoID, vaultAsset, j_)) + + auto applyViewContext = ctx_.getApplyViewContext(); + if (auto ter = removeEmptyHolding(applyViewContext, brokerPseudoID, vaultAsset, j_)) return ter; auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID)); diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index d41ca44e5a..ffe35dd5ea 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -247,8 +247,10 @@ LoanBrokerSet::doApply() return maybePseudo.error(); // LCOV_EXCL_LINE auto& pseudo = *maybePseudo; auto pseudoId = pseudo->at(sfAccount); + + auto applyViewContext = ctx_.getApplyViewContext(); if (auto ter = addEmptyHolding( - ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) + applyViewContext, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) return ter; // Initialize data fields: diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index c025da6852..8d2aa466bb 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -624,8 +624,9 @@ LoanPay::doApply() if (brokerPayee == accountID_) { // The broker may have deleted their holding. Recreate it if needed + auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ter = addEmptyHolding( - ctx_.getApplyViewContext(), + applyViewContext, brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset, diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 4273fc27ea..6f9e587f1b 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -538,12 +538,9 @@ LoanSet::doApply() borrower == accountID_ || borrower == counterparty, "xrpl::LoanSet::doApply", "borrower signed transaction"); + auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ter = addEmptyHolding( - ctx_.getApplyViewContext(), - borrower, - borrowerSle->at(sfBalance).value().xrp(), - vaultAsset, - j_); + applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_); ter && ter != tecDUPLICATE) { // ignore tecDUPLICATE. That means the holding already exists, and @@ -566,7 +563,7 @@ LoanSet::doApply() "broker owner signed transaction"); if (auto const ter = addEmptyHolding( - ctx_.getApplyViewContext(), + applyViewContext, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset, diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index c482c034cc..d408eaa324 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -152,6 +152,7 @@ DepositPreauth::preclaim(PreclaimContext const& ctx) TER DepositPreauth::doApply() { + auto applyViewContext = ctx_.getApplyViewContext(); if (ctx_.tx.isFieldPresent(sfAuthorize)) { auto const sleOwner = view().peek(keylet::account(accountID_)); @@ -161,7 +162,7 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( @@ -209,7 +210,7 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index 7d9f109cf1..220bfde71c 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -203,7 +203,8 @@ PaymentChannelCreate::doApply() // Deduct owner's balance, increment owner count (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + auto applyViewContext = ctx_.getApplyViewContext(); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE adjustOwnerCount(ctx_.view(), sle, *sponsorSle, 1, ctx_.journal); diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index a94cdd4fe9..305857fc39 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -90,7 +90,8 @@ PaymentChannelFund::doApply() { // Check reserve and funds availability auto const balance = (*sle)[sfBalance]; - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + auto applyViewContext = ctx_.getApplyViewContext(); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 59020f6104..b5862cc3b2 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -143,8 +143,9 @@ TER MPTokenAuthorize::doApply() { auto const& tx = ctx_.tx; + auto applyViewContext = ctx_.getApplyViewContext(); return authorizeMPToken( - ctx_.getApplyViewContext(), + applyViewContext, preFeeBalance_, tx[sfMPTokenIssuanceID], accountID_, diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 3235638b75..bb65225f41 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -104,7 +104,7 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) std::expected MPTokenIssuanceCreate::create( - ApplyViewContext&& ctx, + ApplyViewContext& ctx, beast::Journal journal, MPTCreateArgs const& args) { @@ -116,7 +116,7 @@ MPTokenIssuanceCreate::create( SLE::pointer sponsorSle; if (!isPseudoAccount(acct)) { - auto sle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx}); + auto sle = getTxReserveSponsor(ctx); if (!sle) return std::unexpected(sle.error()); sponsorSle = std::move(*sle); @@ -197,8 +197,9 @@ TER MPTokenIssuanceCreate::doApply() { auto const& tx = ctx_.tx; + auto applyViewContext = ctx_.getApplyViewContext(); auto const result = create( - ctx_.getApplyViewContext(), + applyViewContext, j_, { .priorBalance = preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index d97fb14e16..53085e34ff 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -327,7 +327,9 @@ TrustSet::doApply() // but the incremental reserve for the trust line as // well. A person with no intention of using the gateway // could use the extra XRP for their own purposes. - auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + + auto applyViewContext = ctx_.getApplyViewContext(); + auto const sponsorSle = getTxReserveSponsor(applyViewContext); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index f5ff6d2aa5..e77acdc81c 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -399,8 +399,9 @@ VaultClawback::doApply() // Keep MPToken if holder is the vault owner. if (holder != vault->at(sfOwner)) { + auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ter = - removeEmptyHolding(ctx_.getApplyViewContext(), holder, sharesDestroyed.asset(), j_); + removeEmptyHolding(applyViewContext, holder, sharesDestroyed.asset(), j_); isTesSuccess(ter)) { JLOG(j_.debug()) // diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index 755991d659..073a37eff8 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -146,6 +146,7 @@ VaultCreate::doApply() // we can consider downgrading them to `tef` or `tem`. auto const& tx = ctx_.tx; + auto applyViewContext = ctx_.getApplyViewContext(); auto const sequence = tx.getSeqValue(); auto const owner = view().peek(keylet::account(accountID_)); if (owner == nullptr) @@ -167,7 +168,7 @@ VaultCreate::doApply() AccountID const pseudoId = pseudo->at(sfAccount); auto const asset = tx[sfAsset]; - if (auto ter = addEmptyHolding(ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, asset, j_); + if (auto ter = addEmptyHolding(applyViewContext, pseudoId, preFeeBalance_, asset, j_); !isTesSuccess(ter)) return ter; @@ -197,7 +198,7 @@ VaultCreate::doApply() : keylet::line(pseudoId, asset.get()).key; }(); auto const maybeShare = MPTokenIssuanceCreate::create( - ctx_.getApplyViewContext(), + applyViewContext, j_, { .priorBalance = std::nullopt, @@ -244,7 +245,7 @@ VaultCreate::doApply() // Explicitly create MPToken for the vault owner if (auto const err = authorizeMPToken( - ctx_.getApplyViewContext(), preFeeBalance_, mptIssuanceID, accountID_, ctx_.journal); + applyViewContext, preFeeBalance_, mptIssuanceID, accountID_, ctx_.journal); !isTesSuccess(err)) return err; @@ -252,7 +253,7 @@ VaultCreate::doApply() if (tx.isFlag(tfVaultPrivate)) { if (auto const err = authorizeMPToken( - ctx_.getApplyViewContext(), + applyViewContext, preFeeBalance_, mptIssuanceID, pseudoId, diff --git a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index 69c9cfa72b..12191bb8d7 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -88,13 +88,14 @@ TER VaultDelete::doApply() { auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE // Destroy the asset holding. auto asset = vault->at(sfAsset); - if (auto ter = removeEmptyHolding(ctx_.getApplyViewContext(), vault->at(sfAccount), asset, j_); + if (auto ter = removeEmptyHolding(applyViewContext, vault->at(sfAccount), asset, j_); !isTesSuccess(ter)) return ter; @@ -123,8 +124,8 @@ VaultDelete::doApply() // Try to remove MPToken for vault shares for the vault owner if it exists. if (auto const mptoken = view().peek(keylet::mptoken(shareMPTID, accountID_))) { - if (auto const ter = removeEmptyHolding( - ctx_.getApplyViewContext(), accountID_, MPTIssue(shareMPTID), j_); + if (auto const ter = + removeEmptyHolding(applyViewContext, accountID_, MPTIssue(shareMPTID), j_); !isTesSuccess(ter)) { // LCOV_EXCL_START diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index 78a1c11df8..569e8d6983 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -187,6 +187,7 @@ VaultDeposit::doApply() { bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0); auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE auto const vaultAsset = vault->at(sfAsset); @@ -221,7 +222,7 @@ VaultDeposit::doApply() if (vault->isFlag(lsfVaultPrivate) && accountID_ != vault->at(sfOwner)) { if (auto const err = enforceMPTokenAuthorization( - ctx_.getApplyViewContext(), mptIssuanceID, accountID_, preFeeBalance_, j_); + applyViewContext, mptIssuanceID, accountID_, preFeeBalance_, j_); !isTesSuccess(err)) return err; } @@ -231,7 +232,7 @@ VaultDeposit::doApply() if (!view().exists(keylet::mptoken(mptIssuanceID, accountID_))) { if (auto const err = authorizeMPToken( - ctx_.getApplyViewContext(), + applyViewContext, preFeeBalance_, mptIssuanceID->value(), accountID_, @@ -247,7 +248,7 @@ VaultDeposit::doApply() XRPL_ASSERT( accountID_ == vault->at(sfOwner), "xrpl::VaultDeposit::doApply : account is owner"); if (auto const err = authorizeMPToken( - ctx_.getApplyViewContext(), + applyViewContext, preFeeBalance_, // priorBalance mptIssuanceID->value(), // mptIssuanceID sleIssuance->at(sfIssuer), // account diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index 9955fbb02d..209b5d4273 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -176,6 +176,7 @@ TER VaultWithdraw::doApply() { auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); + auto applyViewContext = ctx_.getApplyViewContext(); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE @@ -337,8 +338,8 @@ VaultWithdraw::doApply() // Keep MPToken if holder is the vault owner. if (accountID_ != vault->at(sfOwner)) { - if (auto const ter = removeEmptyHolding( - ctx_.getApplyViewContext(), accountID_, sharesRedeemed.asset(), j_); + if (auto const ter = + removeEmptyHolding(applyViewContext, accountID_, sharesRedeemed.asset(), j_); isTesSuccess(ter)) { JLOG(j_.debug()) // @@ -365,13 +366,7 @@ VaultWithdraw::doApply() associateAsset(*vault, vaultAsset); return doWithdraw( - ctx_.getApplyViewContext(), - accountID_, - dstAcct, - vaultAccount, - preFeeBalance_, - assetsWithdrawn, - j_); + applyViewContext, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_); } void From 09b5cd98797d42ef67f6afc3bc9e58bae5391e3f Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 14:06:20 -0400 Subject: [PATCH 07/16] refactor: Move `SponsorshipTransfer` code around, add some comments (#7662) --- include/xrpl/ledger/helpers/OracleHelpers.h | 19 +++ include/xrpl/ledger/helpers/SponsorHelpers.h | 96 ++++++++++++++ .../xrpl/tx/transactors/oracle/OracleSet.h | 6 - .../tx/invariants/SponsorshipInvariant.cpp | 4 +- .../Sponsor/SponsorshipTransfer.cpp | 118 ++++-------------- src/test/app/Sponsor_test.cpp | 1 + 6 files changed, 142 insertions(+), 102 deletions(-) create mode 100644 include/xrpl/ledger/helpers/OracleHelpers.h diff --git a/include/xrpl/ledger/helpers/OracleHelpers.h b/include/xrpl/ledger/helpers/OracleHelpers.h new file mode 100644 index 0000000000..635e47c564 --- /dev/null +++ b/include/xrpl/ledger/helpers/OracleHelpers.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace xrpl { + +constexpr uint32_t kMinOracleReserveCount = 1; +constexpr uint32_t kMaxOracleReserveCount = 2; +constexpr std::size_t kOracleReserveCountThreshold = 5; + +inline uint32_t +calculateOracleReserve(std::size_t priceDataSeriesCount) +{ + return priceDataSeriesCount > kOracleReserveCountThreshold ? kMaxOracleReserveCount + : kMinOracleReserveCount; +} + +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h index 65dad72897..93614e5e17 100644 --- a/include/xrpl/ledger/helpers/SponsorHelpers.h +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -123,4 +124,99 @@ removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field = sfSponsor) sle->makeFieldAbsent(field); } +template +inline std::optional +getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account) +{ + switch (sle->getType()) + { + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + case ltMPTOKEN: + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + return sle->getAccountID(sfAccount); + case ltMPTOKEN_ISSUANCE: + return sle->getAccountID(sfIssuer); + case ltSIGNER_LIST: { + auto const signerList = view.read(keylet::signers(account)); + if (!signerList) + return std::nullopt; + if (signerList->key() == sle->key()) + return account; + return std::nullopt; + } + case ltCREDENTIAL: { + if (sle->isFlag(lsfAccepted)) + return sle->getAccountID(sfSubject); + return sle->getAccountID(sfIssuer); + } + case ltRIPPLE_STATE: { + if (sle->isFlag(lsfHighReserve)) + { + auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer(); + if (highAccount == account) + return highAccount; + } + if (sle->isFlag(lsfLowReserve)) + { + auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer(); + if (lowAccount == account) + return lowAccount; + } + return std::nullopt; + } + default: + UNREACHABLE("Object is not supported by sponsorship."); + return std::nullopt; + }; +} + +template +inline std::uint32_t +getLedgerEntryOwnerCount(T const& sle) +{ + switch (sle->getType()) + { + case ltORACLE: { + return calculateOracleReserve(sle->getFieldArray(sfPriceDataSeries).size()); + } + // Vaults require 2 owner counts (the vault and a pseudo-account) + case ltVAULT: + return 2; + default: + return 1; + } +}; + +template +inline SF_ACCOUNT const& +getLedgerEntrySponsorField(T const& sle, AccountID const& owner) +{ + switch (sle->getType()) + { + case ltRIPPLE_STATE: { + if (sle->isFlag(lsfHighReserve)) + { + auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer(); + if (highAccount == owner) + return sfHighSponsor; + } + if (sle->isFlag(lsfLowReserve)) + { + auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer(); + if (lowAccount == owner) + return sfLowSponsor; + } + // LCOV_EXCL_START + UNREACHABLE("Should not happen. Owner should be checked before calling this function."); + return sfSponsor; + // LCOV_EXCL_STOP + } + default: + return sfSponsor; + } +}; + } // namespace xrpl diff --git a/include/xrpl/tx/transactors/oracle/OracleSet.h b/include/xrpl/tx/transactors/oracle/OracleSet.h index 51ed7be3dc..831c11b8c4 100644 --- a/include/xrpl/tx/transactors/oracle/OracleSet.h +++ b/include/xrpl/tx/transactors/oracle/OracleSet.h @@ -22,12 +22,6 @@ public: { } - static uint32_t - calculateOracleReserve(std::size_t count) - { - return count > 5 ? 2 : 1; - } - static NotTEC preflight(PreflightContext const& ctx); diff --git a/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp index f4db96a643..9ebc008473 100644 --- a/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp +++ b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp @@ -4,13 +4,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include @@ -62,7 +62,7 @@ SponsorshipOwnerCountsMatch::visitEntry( if (!sle->isFieldPresent(sfSponsor)) return 0; auto const priceDataSeries = sle->getFieldArray(sfPriceDataSeries); - return OracleSet::calculateOracleReserve(priceDataSeries.size()); + return calculateOracleReserve(priceDataSeries.size()); } case ltVAULT: { if (!sle->isFieldPresent(sfSponsor)) diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp index 0c98511a5c..deea6edc84 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp @@ -53,6 +53,9 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx) if (ctx.tx.isFlag(tfSponsorshipCreate)) { + // Sponsor must be included + // SponsorFlags.spfSponsorReserve must be included + // Sponsee must be excluded if (!isReserveSponsored(ctx.tx)) { JLOG(ctx.j.debug()) @@ -68,6 +71,9 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx) } if (ctx.tx.isFlag(tfSponsorshipReassign)) { + // Sponsor must be included + // SponsorFlags.spfSponsorReserve must be included + // Sponsee must be excluded if (!isReserveSponsored(ctx.tx)) { JLOG(ctx.j.debug()) @@ -83,6 +89,8 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx) } if (ctx.tx.isFlag(tfSponsorshipEnd)) { + // Sponsor must be excluded + // SponsorFlags.spfSponsorReserve must be excluded if (isReserveSponsored(ctx.tx)) { JLOG(ctx.j.debug()) @@ -116,96 +124,18 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx) return tesSUCCESS; } -template -inline std::optional -getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account) -{ - switch (sle->getType()) - { - case ltCHECK: - case ltESCROW: - case ltPAYCHAN: - case ltMPTOKEN: - case ltDELEGATE: - case ltDEPOSIT_PREAUTH: - return sle->getAccountID(sfAccount); - case ltMPTOKEN_ISSUANCE: - return sle->getAccountID(sfIssuer); - case ltSIGNER_LIST: { - auto const signerList = view.read(keylet::signers(account)); - if (!signerList) - return std::nullopt; - if (signerList->key() == sle->key()) - return account; - return std::nullopt; - } - case ltCREDENTIAL: { - if (sle->isFlag(lsfAccepted)) - return sle->getAccountID(sfSubject); - return sle->getAccountID(sfIssuer); - } - case ltRIPPLE_STATE: { - if (sle->isFlag(lsfHighReserve)) - { - auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer(); - if (highAccount == account) - return highAccount; - } - if (sle->isFlag(lsfLowReserve)) - { - auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer(); - if (lowAccount == account) - return lowAccount; - } - return std::nullopt; - } - default: - UNREACHABLE("Object is not supported by sponsorship."); - return std::nullopt; - }; -} - -template -inline SF_ACCOUNT const& -getLedgerEntrySponsorField(T const& sle, AccountID const& owner) -{ - switch (sle->getType()) - { - case ltRIPPLE_STATE: { - if (sle->isFlag(lsfHighReserve)) - { - auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer(); - if (highAccount == owner) - return sfHighSponsor; - } - if (sle->isFlag(lsfLowReserve)) - { - auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer(); - if (lowAccount == owner) - return sfLowSponsor; - } - // LCOV_EXCL_START - UNREACHABLE("Should not happen. Owner should be checked before calling this function."); - return sfSponsor; - // LCOV_EXCL_STOP - } - default: - return sfSponsor; - } -}; - TER SponsorshipTransfer::preclaim(PreclaimContext const& ctx) { auto const index = ctx.tx[~sfObjectID]; - auto const newSponsorSle = getTxReserveSponsor(ctx.view, ctx.tx); - if (!newSponsorSle) - return newSponsorSle.error(); // LCOV_EXCL_LINE + auto const newSponsorSleExpected = getTxReserveSponsor(ctx.view, ctx.tx); + if (!newSponsorSleExpected) + return newSponsorSleExpected.error(); // LCOV_EXCL_LINE + auto const newSponsorSle = *newSponsorSleExpected; - bool const isObjectSponsor = index != std::nullopt; + bool const isObjectSponsor = !!index; auto const account = ctx.tx[sfAccount]; - auto const sponseeID = ctx.tx[~sfSponsee].value_or(account); auto const sponseeSle = ctx.view.read(keylet::account(sponseeID)); if (!sponseeSle) @@ -243,23 +173,23 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) std::uint32_t const ownerCountDelta = 1; auto const owner = getLedgerEntryOwner(ctx.view, sle, sponseeID); - if (!owner || owner != sponseeID) + if (!owner.has_value() || owner.value() != sponseeID) return tecNO_PERMISSION; - auto const& sponsorField = getLedgerEntrySponsorField(sle, *owner); + auto const& sponsorField = getLedgerEntrySponsorField(sle, owner.value()); if (ctx.tx.isFlag(tfSponsorshipCreate)) { - if (!*newSponsorSle) + if (!newSponsorSle) return tecNO_PERMISSION; - // check object is not sponsored yet + // check that the object is not sponsored yet if (sle->isFieldPresent(sponsorField)) return tecNO_PERMISSION; } else if (ctx.tx.isFlag(tfSponsorshipReassign)) { - if (!*newSponsorSle) + if (!newSponsorSle) return tecNO_PERMISSION; // check object is already ctx.sponsored @@ -268,7 +198,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) } else if (ctx.tx.isFlag(tfSponsorshipEnd)) { - if (*newSponsorSle) + if (newSponsorSle) return tecNO_PERMISSION; // check object is sponsored @@ -288,7 +218,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) ctx.tx, sponseeSle, sponseeSle->getFieldAmount(sfBalance), - *newSponsorSle, + newSponsorSle, ownerCountDelta, 0, ctx.j); @@ -299,7 +229,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) { if (ctx.tx.isFlag(tfSponsorshipCreate)) { - if (!*newSponsorSle) + if (!newSponsorSle) return tecNO_PERMISSION; // check account is not sponsored yet @@ -308,7 +238,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) } else if (ctx.tx.isFlag(tfSponsorshipReassign)) { - if (!*newSponsorSle) + if (!newSponsorSle) return tecNO_PERMISSION; // check account is already sponsored @@ -317,7 +247,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) } else if (ctx.tx.isFlag(tfSponsorshipEnd)) { - if (*newSponsorSle) + if (newSponsorSle) return tecNO_PERMISSION; // check account is sponsored @@ -340,7 +270,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) ctx.tx, sponseeSle, sponseeSle->getFieldAmount(sfBalance), - *newSponsorSle, + newSponsorSle, 0, 1, ctx.j); diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 6b974c28a1..a615426463 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include From efeedcdc6006623ebf7d6321ca147ba031a40c75 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Tue, 30 Jun 2026 14:21:10 -0400 Subject: [PATCH 08/16] fix: Disallow sponsored tx to be queued (#7674) --- src/test/app/TxQ_test.cpp | 40 +++++++++++++++++++++++++++++++ src/xrpld/app/misc/detail/TxQ.cpp | 5 ++++ 2 files changed, 45 insertions(+) diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 0ae6b4d80a..330706dca6 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -2335,6 +2337,43 @@ public: BEAST_EXPECT(env.balance(alice) == drops(5)); } + void + testSponsorTxCannotQueue() + { + using namespace jtx; + testcase("disallow sponsored transaction from being queued"); + + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); + + auto sponsor = Account("sponsor"); + auto sponsee = Account("sponsee"); + auto filler = Account("filler"); + + env.fund(XRP(50000), noripple(sponsor, sponsee)); + env.close(); + env.fund(XRP(50000), noripple(filler)); + env.close(); + + fillQueue(env, filler); + checkMetrics(*this, env, 0, 6, 4, 3); + + // Sponsored transactions are not allowed to be queued. + env(noop(sponsee), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Ter(telCAN_NOT_QUEUE)); + checkMetrics(*this, env, 0, 6, 4, 3); + + // Sponsored transactions may still apply directly if they pay the + // open ledger fee. They just cannot be held in the queue. + env(noop(sponsee), + sponsor::As(sponsor, spfSponsorFee), + Sig(sfSponsorSignature, sponsor), + Fee(openLedgerCost(env)), + Ter(tesSUCCESS)); + checkMetrics(*this, env, 0, 6, 5, 3); + } + void testConsequences() { @@ -4662,6 +4701,7 @@ public: testBlockersSeq(); testBlockersTicket(); testInFlightBalance(); + testSponsorTxCannotQueue(); testConsequences(); } diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index 0326828a70..b4fb40f368 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -398,6 +399,10 @@ TxQ::canBeHeld( ((flags & TapFailHard) != 0u)) return telCAN_NOT_QUEUE; + // Disallow sponsored transactions from being queued. + if (tx.isFieldPresent(sfSponsor) && isFeeSponsored(tx)) + return telCAN_NOT_QUEUE; + { // To be queued and relayed, the transaction needs to // promise to stick around for long enough that it has From 0832b1cfe8b2557b6c1b3814fb5a1e413a1a7bf4 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 14:33:23 -0400 Subject: [PATCH 09/16] fix clang-tidy --- src/libxrpl/tx/transactors/check/CheckCash.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index 257571e5d7..1c12823b9c 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include From 140d5ec297a1450cf0dba11bd9045f3e2cf161c6 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 17:20:07 -0400 Subject: [PATCH 10/16] fix: crash in reset (#7675) --- src/libxrpl/tx/Transactor.cpp | 33 +++++++++++++++++-- src/test/app/Sponsor_test.cpp | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index c38109bece..8ce60f214b 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -640,7 +640,28 @@ Transactor::payFee() if (!sle) return tefINTERNAL; // LCOV_EXCL_LINE - auto const feeAmountAfter = sle->getFieldAmount(feePayer.balanceField) - feePaid; + if (feePaid == beast::kZero) + return tesSUCCESS; + + XRPAmount balance = beast::kZero; + if (sle->isFieldPresent(feePayer.balanceField)) + { + balance = sle->getFieldAmount(feePayer.balanceField).xrp(); + } + else if (feePayer.balanceField != sfFeeAmount) + { + return tefINTERNAL; // LCOV_EXCL_LINE + } + + if (feePaid > balance) + { + if ((balance > beast::kZero) && !view().open()) + return tecINSUFF_FEE; + + return terINSUF_FEE_B; + } + + auto const feeAmountAfter = balance - feePaid; if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount) { @@ -1302,7 +1323,15 @@ Transactor::reset(XRPAmount fee) if (!payerSle) return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE - auto const balance = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + XRPAmount balance = beast::kZero; + if (payerSle->isFieldPresent(feePayer.balanceField)) + { + balance = payerSle->getFieldAmount(feePayer.balanceField).xrp(); + } + else if (feePayer.balanceField != sfFeeAmount) + { + return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE + } if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee)) { diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index a615426463..a1d300dddc 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,7 @@ #include #include #include +#include #include #include @@ -1835,6 +1837,66 @@ public: BEAST_EXPECT(sle->getFieldAmount(sfFeeAmount) == drops(990)); // 1000 - MaxFee(10) } + // LedgerStateFix charges an owner-reserve fee and can claim that fee + // while returning tecFAILED_PROCESSING. That path must be safe when the + // fee is pre-funded by a sponsorship object. + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(1000), alice, sponsor); + env.close(); + + auto const fixFee = drops(env.current()->fees().increment); + env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice)); + env.close(); + + env(ledgerStateFix::nftPageLinks(alice, alice), + Fee(fixFee), + sponsor::As(sponsor, spfSponsorFee), + Ter(tecFAILED_PROCESSING)); + + if (auto const sle = env.le(keylet::sponsorship(sponsor, alice)); BEAST_EXPECT(sle)) + BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount)); + } + + // If preclaim saw spendable sponsored FeeAmount but the apply view no + // longer has it, the fee path should fail cleanly instead of throwing. + { + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(1000), alice, sponsor); + env.close(); + + auto const fixFee = drops(env.current()->fees().increment); + env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice)); + env.close(); + + OpenView overlay(&*env.closed()); + auto jt = env.jt( + ledgerStateFix::nftPageLinks(alice, alice), + Fee(fixFee), + sponsor::As(sponsor, spfSponsorFee)); + + auto const pf = preflight(env.app(), overlay.rules(), *jt.stx, TapNone, env.journal); + BEAST_EXPECT(isTesSuccess(pf.ter)); + auto const pc = preclaim(pf, env.app(), overlay); + BEAST_EXPECT(isTesSuccess(pc.ter)); + + auto const original = overlay.read(keylet::sponsorship(sponsor, alice)); + if (BEAST_EXPECT(original)) + { + auto sle = std::make_shared(*original); + sle->makeFieldAbsent(sfFeeAmount); + overlay.rawReplace(sle); + } + + auto const result = doApply(pc, env.app(), overlay); + BEAST_EXPECT(result.ter == terINSUF_FEE_B); + BEAST_EXPECT(!result.applied); + } + // test lsfSponsorshipRequireSignForFee { Env env{*this, testableAmendments()}; From 4e6f8f334f7e8b006e557c036124fb7b3f2b07c0 Mon Sep 17 00:00:00 2001 From: Kassaking7 <96991820+Kassaking7@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:24:33 -0400 Subject: [PATCH 11/16] fix: add missing tests to cover all scenario for sponsor reserve (#7574) --- src/test/app/Sponsor_test.cpp | 205 ++++++++++++++++++++++++++++++++-- 1 file changed, 198 insertions(+), 7 deletions(-) diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index a1d300dddc..d0f8a60654 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +60,10 @@ #include #include #include +#include #include +#include +#include namespace xrpl::test { @@ -2231,10 +2236,11 @@ public: std::optional> expected = std::nullopt) { using namespace test::jtx; - // auto const sponsorOwnerCountBefore = ownerCount(env, sponsor); auto const sponseeOwnerCountBefore = ownerCount(env, sponsee); auto const sponseeSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsee); auto const sponseeSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsee); + auto const sponsorOwnerCountBefore = ownerCount(env, sponsor); + auto const sponsorSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsor); auto const sponsorSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsor); std::optional sponsorSig = @@ -2274,7 +2280,7 @@ public: env.close(); } - if (sponsorReserveCount - 1 > 0) + if (sponsorReserveCount > 1) { env(sponsor::set(sponsor, 0, sponsorReserveCount - 1, XRP(1)), sponsor::SponseeAcc(sponsee)); @@ -2287,8 +2293,53 @@ public: } env.close(); } + + // A failed sponsored create must not consume prefunded reserve or mutate owner counts. + auto const sponseeOwnerCountBeforeAttempt = ownerCount(env, sponsee); + auto const sponseeSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsee); + auto const sponseeSponsoringOwnerCountBeforeAttempt = + sponsoringOwnerCount(env, sponsee); + auto const sponsorOwnerCountBeforeAttempt = ownerCount(env, sponsor); + auto const sponsorSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsor); + auto const sponsorSponsoringOwnerCountBeforeAttempt = + sponsoringOwnerCount(env, sponsor); + auto const sponsorshipSleBeforeAttempt = env.le(keylet::sponsorship(sponsor, sponsee)); + bool const reserveCountPresentBeforeAttempt = sponsorshipSleBeforeAttempt && + sponsorshipSleBeforeAttempt->isFieldPresent(sfRemainingOwnerCount); + std::uint32_t const reserveCountBeforeAttempt = reserveCountPresentBeforeAttempt + ? sponsorshipSleBeforeAttempt->getFieldU32(sfRemainingOwnerCount) + : 0; + callback(env, submit(insufficientReserveResult)); env.close(); + + BEAST_EXPECT(ownerCount(env, sponsee) == sponseeOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsee) == sponseeSponsoredOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsee) == sponseeSponsoringOwnerCountBeforeAttempt); + BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBeforeAttempt); + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsor) == sponsorSponsoringOwnerCountBeforeAttempt); + + auto const sponsorshipSleAfterAttempt = env.le(keylet::sponsorship(sponsor, sponsee)); + BEAST_EXPECT( + static_cast(sponsorshipSleAfterAttempt) == + static_cast(sponsorshipSleBeforeAttempt)); + if (sponsorshipSleAfterAttempt) + { + BEAST_EXPECT( + sponsorshipSleAfterAttempt->isFieldPresent(sfRemainingOwnerCount) == + reserveCountPresentBeforeAttempt); + if (reserveCountPresentBeforeAttempt) + { + BEAST_EXPECT( + sponsorshipSleAfterAttempt->getFieldU32(sfRemainingOwnerCount) == + reserveCountBeforeAttempt); + } + } } // Success @@ -2311,6 +2362,13 @@ public: if (!cosigning) { + // Prefunded success consumes the reserved owner slot before cleanup. + auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, sponsee)); + BEAST_EXPECT(sponsorshipSle); + BEAST_EXPECT( + !sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) || + sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0); + // cleanup sponsorship env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee)); env.close(); @@ -2329,6 +2387,8 @@ public: sponsorReserveCount); BEAST_EXPECT( sponsoringOwnerCount(env, sponsee) - sponseeSponsoringOwnerCountBefore == 0); + BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBefore); + BEAST_EXPECT(sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBefore); BEAST_EXPECT( sponsoringOwnerCount(env, sponsor) - sponsorSponsoringOwnerCountBefore == sponsorReserveCount); @@ -2571,6 +2631,7 @@ public: Account const alice("alice"); Account const sponsor("sponsor"); Account const sponsor2("sponsor2"); + auto const credType = std::string("credType"); { Env env{*this, testableAmendments()}; @@ -2598,6 +2659,114 @@ public: env.close(); } else + { + env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); + env.close(); + // No sponsor signature here: this exercises the prefunded reassign path. + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve)); + env.close(); + + auto const sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice)); + BEAST_EXPECT(sponsor2Sle); + if (sponsor2Sle) + { + BEAST_EXPECT( + !sponsor2Sle->isFieldPresent(sfRemainingOwnerCount) || + sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 0); + } + } + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + // DepositPreauthDelete + env(deposit::unauth(alice, sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, sponsor); + env.close(); + auto const authCreds = std::vector{ + {.issuer = sponsor, .credType = credType}}; + auto const preauthKeylet = keylet::depositPreauth( + alice.id(), + std::set>{ + {sponsor.id(), Slice(credType.data(), credType.size())}}); + + // Cover DepositPreauth's sfAuthorizeCredentials sponsor-reserve branch. + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env&, auto const& submit) { + submit(deposit::authCredentials(alice, authCreds)); + }); + + // Cover sfUnauthorizeCredentials cleanup for a sponsored preauth object. + BEAST_EXPECT(env.le(preauthKeylet)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + env(deposit::unauthCredentials(alice, authCreds)); + env.close(); + + BEAST_EXPECT(!env.le(preauthKeylet)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + + void + testDID(bool cosigning) + { + testcase("DID"); + using namespace test::jtx; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + { + Env env{*this, testableAmendments()}; + env.fund(XRP(1000000), alice, sponsor, sponsor2); + env.close(); + + // DIDSet + testEachSponsorship( + env, + cosigning, + sponsor, + alice, + 1, + 1, + tecINSUFFICIENT_RESERVE, + [&](Env& env, auto const& submit) { submit(did::set(alice), did::Uri("uri")); }); + + // transfer sponsor + auto const keylet = keylet::did(alice); + if (cosigning) + { + env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), + sponsor::As(sponsor2, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor2)); + env.close(); + } + else { env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); env.close(); @@ -2615,11 +2784,15 @@ public: auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor2, alice)); BEAST_EXPECT(sponsorshipSle); if (sponsorshipSle) - BEAST_EXPECT(sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0); + { + BEAST_EXPECT( + !sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) || + sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0); + } } - // DepositPreauthDelete - env(deposit::unauth(alice, sponsor)); + // DIDDelete + env(did::del(alice)); env.close(); BEAST_EXPECT(ownerCount(env, alice) == 0); @@ -3490,6 +3663,12 @@ public: auto const requiredFee = drops(env.current()->fees().increment); env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecNO_SPONSOR_PERMISSION)); + // The failed delete must leave the account sponsored by the original sponsor. + auto const aliceSle = env.le(keylet::account(alice)); + BEAST_EXPECT(aliceSle); + if (aliceSle) + BEAST_EXPECT(aliceSle->getAccountID(sfSponsor) == sponsor.id()); + auto const sponsorSle = env.le(keylet::account(sponsor)); BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1); @@ -3533,13 +3712,19 @@ public: // Verify sfSponsoringOwnerCount is set on sponsor auto const sponsorSle = env.le(keylet::account(sponsor)); BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringOwnerCount)); - BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) >= 1); + auto const sponsoringOwnerCount = sponsorSle->getFieldU32(sfSponsoringOwnerCount); + BEAST_EXPECT(sponsoringOwnerCount >= 1); incLgrSeqForAccDel(env, sponsor); // AccountDelete should fail auto const requiredFee = drops(env.current()->fees().increment); env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + // The failed delete must not decrement the outstanding sponsored-object count. + auto const sponsorSleAfter = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringOwnerCount)); + BEAST_EXPECT( + sponsorSleAfter->getFieldU32(sfSponsoringOwnerCount) == sponsoringOwnerCount); } { @@ -3556,13 +3741,19 @@ public: // Verify sfSponsoringAccountCount is set on sponsor auto const sponsorSle = env.le(keylet::account(sponsor)); BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringAccountCount)); - BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1); + auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount); + BEAST_EXPECT(sponsoringAccountCount == 1); incLgrSeqForAccDel(env, sponsor); // AccountDelete should fail auto const requiredFee = drops(env.current()->fees().increment); env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS)); + // The failed delete must not decrement the outstanding sponsored-account count. + auto const sponsorSleAfter = env.le(keylet::account(sponsor)); + BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringAccountCount)); + BEAST_EXPECT( + sponsorSleAfter->getFieldU32(sfSponsoringAccountCount) == sponsoringAccountCount); } } From 8f91699943ccf8693d6f7c4539422b21c5f7ce97 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 17:33:00 -0400 Subject: [PATCH 12/16] Revert "use reference in helper functions" This reverts commit b8ae7a281386e8f349cc65aac5967de276160616. --- include/xrpl/ledger/helpers/EscrowHelpers.h | 6 +++--- include/xrpl/ledger/helpers/MPTokenHelpers.h | 8 ++++---- include/xrpl/ledger/helpers/RippleStateHelpers.h | 4 ++-- include/xrpl/ledger/helpers/SponsorHelpers.h | 2 +- include/xrpl/ledger/helpers/TokenHelpers.h | 4 ++-- include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h | 2 +- src/libxrpl/ledger/View.cpp | 2 +- src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 8 ++++---- src/libxrpl/ledger/helpers/RippleStateHelpers.cpp | 4 ++-- src/libxrpl/ledger/helpers/TokenHelpers.cpp | 4 ++-- .../tx/transactors/token/MPTokenIssuanceCreate.cpp | 2 +- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index 533daa0cf2..a80a290b84 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -17,7 +17,7 @@ namespace xrpl { template TER escrowUnlockApplyHelper( - ApplyViewContext& ctx, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, @@ -31,7 +31,7 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyViewContext& ctx, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, @@ -168,7 +168,7 @@ escrowUnlockApplyHelper( template <> inline TER escrowUnlockApplyHelper( - ApplyViewContext& ctx, + ApplyViewContext ctx, Rate lockedRate, SLE::ref sleDest, STAmount const& xrpBalance, diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index cbdffdfc91..a609c47f99 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -71,7 +71,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue); [[nodiscard]] TER authorizeMPToken( - ApplyViewContext& ctx, + ApplyViewContext ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -102,7 +102,7 @@ requireAuth( */ [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyViewContext& ctx, + ApplyViewContext ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, @@ -188,7 +188,7 @@ canMPTTradeAndTransfer( [[nodiscard]] TER addEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -196,7 +196,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal); diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index 3cd5b79eee..53c843ec1d 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -229,7 +229,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc /// canAddHolding() in preflight with the same View and Asset [[nodiscard]] TER addEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -237,7 +237,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal); diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h index e1696c7a9b..be73b3252c 100644 --- a/include/xrpl/ledger/helpers/SponsorHelpers.h +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -35,7 +35,7 @@ getTxReserveSponsorAccountID(STTx const& tx) } inline std::expected -getTxReserveSponsor(ApplyViewContext& ctx) +getTxReserveSponsor(ApplyViewContext ctx) { auto const sponsorID = getTxReserveSponsorAccountID(ctx.tx); if (sponsorID) diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index 70d4ecab58..c8c42a8c80 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -230,7 +230,7 @@ canAddHolding(ReadView const& view, Asset const& asset); [[nodiscard]] TER addEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -238,7 +238,7 @@ addEmptyHolding( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal); diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index 0b3acb5d79..8d4cea1542 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -63,7 +63,7 @@ public: beast::Journal const& j) override; static std::expected - create(ApplyViewContext& ctx, beast::Journal journal, MPTCreateArgs const& args); + create(ApplyViewContext ctx, beast::Journal journal, MPTCreateArgs const& args); }; } // namespace xrpl diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 51b0fcdb4c..de3994ec88 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -432,7 +432,7 @@ canWithdraw(ReadView const& view, STTx const& tx) TER doWithdraw( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 9acb15d7ae..dc394da2cf 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -126,7 +126,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue) [[nodiscard]] TER addEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -149,7 +149,7 @@ addEmptyHolding( [[nodiscard]] TER authorizeMPToken( - ApplyViewContext& ctx, + ApplyViewContext ctx, XRPAmount const& priorBalance, MPTID const& mptIssuanceID, AccountID const& account, @@ -286,7 +286,7 @@ authorizeMPToken( [[nodiscard]] TER removeEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal) @@ -418,7 +418,7 @@ requireAuth( [[nodiscard]] TER enforceMPTokenAuthorization( - ApplyViewContext& ctx, + ApplyViewContext ctx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, // for MPToken authorization diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 4058ca89cc..ffb6b2aef2 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -633,7 +633,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc TER addEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Issue const& issue, @@ -702,7 +702,7 @@ addEmptyHolding( TER removeEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, Issue const& issue, beast::Journal journal) diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 482f62d468..2cf27c1fce 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -473,7 +473,7 @@ canAddHolding(ReadView const& view, Asset const& asset) TER addEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, XRPAmount priorBalance, Asset const& asset, @@ -488,7 +488,7 @@ addEmptyHolding( TER removeEmptyHolding( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& accountID, Asset const& asset, beast::Journal journal) diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index bb65225f41..7574018b7f 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -104,7 +104,7 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) std::expected MPTokenIssuanceCreate::create( - ApplyViewContext& ctx, + ApplyViewContext ctx, beast::Journal journal, MPTCreateArgs const& args) { From 9f5daa9b8b5e0338f6ac62b30daa83636766daf0 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 30 Jun 2026 17:47:36 -0400 Subject: [PATCH 13/16] fix build --- include/xrpl/ledger/View.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 6643ab2cb1..20de0634a9 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -201,7 +201,7 @@ canWithdraw(ReadView const& view, STTx const& tx); [[nodiscard]] TER doWithdraw( - ApplyViewContext& ctx, + ApplyViewContext ctx, AccountID const& senderAcct, AccountID const& dstAcct, AccountID const& sourceAcct, From 730e526080f9cac0430c52ec7d752e1975a5a9a6 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 1 Jul 2026 11:37:45 -0400 Subject: [PATCH 14/16] fix: Better handling of txs that send XRP + charge reserve (#7671) --- .../tx/transactors/escrow/EscrowCreate.cpp | 26 +++++- .../payment_channel/PaymentChannelCreate.cpp | 32 +++++-- src/test/app/Sponsor_test.cpp | 86 +++++++++++++++++++ 3 files changed, 133 insertions(+), 11 deletions(-) diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index bdb4bcddae..89142baf8a 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -439,16 +440,37 @@ EscrowCreate::doApply() auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE + // First check: whoever is on the hook for the new owner increment + // can cover it. When sponsored this hits the sponsor branch and + // validates the sponsor's reserve + remaining credit. When + // unsponsored this hits the source branch and validates the + // source's pre-lock balance against base + (currentOC+1)*increment. if (auto const ret = checkInsufficientReserve(ctx_.view(), ctx_.tx, sle, balance, *sponsorSle, 1, 0, j_); !isTesSuccess(ret)) return ret; - // Check reserve and funds availability if (isXRP(amount)) { + // Second check (XRP escrow only): after locking the escrowed + // amount, the source must still meet its own reserve floor. + // Always passes `{}` so the source branch runs (the sponsor's + // reserve was already validated above; here we're verifying the + // source can fund the lock without dipping below its own + // reserve). ownerCountAdj differs by case: + // - sponsored: adj=0 — sponsor covers the new owner increment, + // so the source only owes its base reserve. + // - unsponsored: adj=1 — source owes base + the new increment. + std::int32_t const ownerCountAdj = *sponsorSle ? 0 : 1; if (auto const ret = checkInsufficientReserve( - ctx_.view(), ctx_.tx, sle, balance - STAmount(amount).xrp(), {}, 1, 0, j_); + ctx_.view(), + ctx_.tx, + sle, + balance - STAmount(amount).xrp(), + {}, + ownerCountAdj, + 0, + j_); !isTesSuccess(ret)) return tecUNFUNDED; } diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index a6c1d176bb..f65bc33056 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -22,6 +22,7 @@ #include #include +#include #include namespace xrpl { @@ -134,22 +135,38 @@ PaymentChannelCreate::doApply() return tecEXPIRED; } + auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx); + if (!sponsorSle) + return sponsorSle.error(); // LCOV_EXCL_LINE + if (ctx_.view().rules().enabled(featureSponsor)) { - auto const sponsorSle = getTxReserveSponsor(ctx_.view(), ctx_.tx); - if (!sponsorSle) - return sponsorSle.error(); + // First check: whoever is on the hook for the new owner increment + // can cover it. When sponsored this hits the sponsor branch and + // validates the sponsor's reserve + remaining credit. When + // unsponsored this hits the source branch and validates the + // source's pre-lock balance against base + (currentOC+1)*increment. if (auto const ret = checkInsufficientReserve( - ctx_.view(), ctx_.tx, sle, STAmount{preFeeBalance_}, *sponsorSle, 1, 0, j_); + ctx_.view(), ctx_.tx, sle, preFeeBalance_, *sponsorSle, 1, 0, j_); !isTesSuccess(ret)) return ret; + + // Second check: after locking sfAmount in the channel, the source + // must still meet its own reserve floor. Always passes `{}` so the + // source branch runs (the sponsor's reserve was already validated + // above; here we're verifying the source can fund the lock without + // dipping below its own reserve). ownerCountAdj differs by case: + // - sponsored: adj=0 — sponsor covers the new owner increment, + // so the source only owes its base reserve. + // - unsponsored: adj=1 — source owes base + the new increment. + std::int32_t const ownerCountAdj = *sponsorSle ? 0 : 1; if (auto const ret = checkInsufficientReserve( ctx_.view(), ctx_.tx, sle, - STAmount{preFeeBalance_ - ctx_.tx[sfAmount].xrp()}, + preFeeBalance_ - ctx_.tx[sfAmount].xrp(), {}, - 1, + ownerCountAdj, 0, j_); !isTesSuccess(ret)) @@ -203,9 +220,6 @@ PaymentChannelCreate::doApply() // Deduct owner's balance, increment owner count (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; - auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx); - if (!sponsorSle) - return sponsorSle.error(); // LCOV_EXCL_LINE adjustOwnerCount(ctx_.view(), sle, *sponsorSle, 1, ctx_.journal); addSponsorToLedgerEntry(slep, *sponsorSle); ctx_.view().update(sle); diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index d0f8a60654..74847ea40b 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -2985,6 +2985,50 @@ public: BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); } + + // A sponsored EscrowCreate must still verify that the source + // can fund the escrow amount and stay above its own base + // reserve. The sponsor covers the new object's owner + // increment, but cannot cover the source's base reserve. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // alice's balance is just above the base reserve. After + // locking escrowAmount she would dip below it. + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1)); + + auto const escrowAmount = XRP(2); + auto const seq = env.seq(alice); + + if (cosigning) + { + env(escrow::create(alice, bob, escrowAmount), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED)); + } + else + { + env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + env(escrow::create(alice, bob, escrowAmount), + escrow::kCondition(escrow::kCb1), + escrow::kCancelTime(env.now() + 100s), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecUNFUNDED)); + } + env.close(); + + BEAST_EXPECT(!env.le(keylet::escrow(alice, seq))); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } } void @@ -3251,6 +3295,48 @@ public: BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); } + + // A sponsored PaymentChannelCreate must still verify that the + // source can fund the channel amount and stay above its own + // base reserve. The sponsor covers the new object's owner + // increment, but cannot cover the source's base reserve. + { + Env env{*this, testableAmendments()}; + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + // alice's balance is just above the base reserve. After + // locking channelAmount she would dip below it. + adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1)); + + auto const pk = alice.pk(); + auto const settleDelay = 10s; + auto const channelAmount = XRP(2); + auto const chan = paychan::channel(alice, bob, env.seq(alice)); + + if (cosigning) + { + env(paychan::create(alice, bob, channelAmount, settleDelay, pk), + sponsor::As(sponsor, spfSponsorReserve), + Sig(sfSponsorSignature, sponsor), + Ter(tecUNFUNDED)); + } + else + { + env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice)); + env.close(); + + env(paychan::create(alice, bob, channelAmount, settleDelay, pk), + sponsor::As(sponsor, spfSponsorReserve), + Ter(tecUNFUNDED)); + } + env.close(); + + BEAST_EXPECT(!paychan::channelExists(*env.current(), chan)); + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } } void From 7817245314a1a6bf8854e1bfcd3add16050b08d6 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 1 Jul 2026 13:18:20 -0400 Subject: [PATCH 15/16] refactor: Switch `checkInsufficientReserve` from `ReadView` to `ApplyView` (#7667) --- .../xrpl/ledger/helpers/AccountRootHelpers.h | 2 +- include/xrpl/ledger/helpers/SponsorHelpers.h | 22 +++ .../ledger/helpers/AccountRootHelpers.cpp | 2 +- .../Sponsor/SponsorshipTransfer.cpp | 163 +++++++++++------- 4 files changed, 123 insertions(+), 66 deletions(-) diff --git a/include/xrpl/ledger/helpers/AccountRootHelpers.h b/include/xrpl/ledger/helpers/AccountRootHelpers.h index 403626d06f..6c8233fa61 100644 --- a/include/xrpl/ledger/helpers/AccountRootHelpers.h +++ b/include/xrpl/ledger/helpers/AccountRootHelpers.h @@ -109,7 +109,7 @@ baseAccountReserve(ReadView const& view, std::int32_t ownerCount, std::int32_t a */ [[nodiscard]] TER checkInsufficientReserve( - ReadView const& view, + ApplyView const& view, STTx const& tx, SLE::const_ref accSle, STAmount const& accBalance, diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h index 93614e5e17..4b35e155b4 100644 --- a/include/xrpl/ledger/helpers/SponsorHelpers.h +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -173,6 +173,28 @@ getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account }; } +template +inline bool +isLedgerEntrySupportedBySponsorship(T const& sle) +{ + switch (sle->getType()) + { + case ltCHECK: + case ltESCROW: + case ltPAYCHAN: + case ltMPTOKEN: + case ltDELEGATE: + case ltDEPOSIT_PREAUTH: + case ltMPTOKEN_ISSUANCE: + case ltSIGNER_LIST: + case ltCREDENTIAL: + case ltRIPPLE_STATE: + return true; + default: + return false; + }; +} + template inline std::uint32_t getLedgerEntryOwnerCount(T const& sle) diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 3264fa1a45..26a3a0fabf 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -302,7 +302,7 @@ baseAccountReserve(ReadView const& view, std::int32_t ownerCount, std::int32_t a TER checkInsufficientReserve( - ReadView const& view, + ApplyView const& view, STTx const& tx, SLE::const_ref accSle, STAmount const& accBalance, diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp index deea6edc84..3840c390b1 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -147,30 +146,8 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) if (!sle) return tecNO_ENTRY; - // v1 scope: an object is only sponsorable via SponsorshipTransfer if - // its creating transaction type is itself permitted to set - // spfSponsorReserve (the allow-list in preflight1Sponsor). Otherwise - // an Oracle / Ticket / DID / etc. could be retroactively sponsored - // even though its creating tx cannot be, leaving downstream - // transactors with no path to maintain the sponsorship invariants. - switch (sle->getType()) - { - case ltDELEGATE: - case ltDEPOSIT_PREAUTH: - case ltMPTOKEN: - case ltMPTOKEN_ISSUANCE: - case ltCREDENTIAL: - case ltRIPPLE_STATE: - case ltSIGNER_LIST: - case ltCHECK: - case ltESCROW: - case ltPAYCHAN: - break; - default: - return tecNO_PERMISSION; - } - - std::uint32_t const ownerCountDelta = 1; + if (!isLedgerEntrySupportedBySponsorship(sle)) + return tecNO_PERMISSION; auto const owner = getLedgerEntryOwner(ctx.view, sle, sponseeID); if (!owner.has_value() || owner.value() != sponseeID) @@ -210,20 +187,6 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) if (account != sponsor && account != sponseeID) return tecNO_PERMISSION; } - - // check new sponsor have sufficient balance - // NOLINTNEXTLINE(readability-suspicious-call-argument) - if (auto const ter = checkInsufficientReserve( - ctx.view, - ctx.tx, - sponseeSle, - sponseeSle->getFieldAmount(sfBalance), - newSponsorSle, - ownerCountDelta, - 0, - ctx.j); - !isTesSuccess(ter)) - return ter; } else { @@ -259,23 +222,6 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) if (account != sponsor && account != sponseeID) return tecNO_PERMISSION; } - - // check account have sufficient balance - // In the case of removing an account sponsor, accSle should have no sfSponsor set - // (AccountReserve = 0). However, by setting accountCountDelta = 1 here, we are able to - // calculate the actual required Account Reserve. - // NOLINTNEXTLINE(readability-suspicious-call-argument) - if (auto const ter = checkInsufficientReserve( - ctx.view, - ctx.tx, - sponseeSle, - sponseeSle->getFieldAmount(sfBalance), - newSponsorSle, - 0, - 1, - ctx.j); - !isTesSuccess(ter)) - return ter; } return tesSUCCESS; @@ -338,6 +284,12 @@ SponsorshipTransfer::doApply() return tesSUCCESS; }; + auto const balanceBeforeFee = [&](SLE::const_ref sle) -> STAmount { + if (sle->getAccountID(sfAccount) == accountID_) + return STAmount{preFeeBalance_}; + return sle->getFieldAmount(sfBalance); + }; + if (isObjectSponsor) { auto const hasSignature = tx.isFieldPresent(sfSponsorSignature); @@ -363,6 +315,23 @@ SponsorshipTransfer::doApply() { auto const newSponsorID = tx.getAccountID(sfSponsor); XRPL_ASSERT(!!newSponsorID, "New sponsor is required when creating sponsorship"); + auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); + if (!newSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // check new sponsor have sufficient balance + // NOLINTNEXTLINE(readability-suspicious-call-argument) + if (auto const ter = checkInsufficientReserve( + ctx_.view(), + ctx_.tx, + sponseeSle, + sponseeSle->getFieldAmount(sfBalance), + newSponsorSle, + ownerCountDelta, + 0, + ctx_.journal); + !isTesSuccess(ter)) + return ter; // update owner's sponsored count if (auto const ter = @@ -372,9 +341,6 @@ SponsorshipTransfer::doApply() view().update(ownerSle); // increment new sponsor's sponsoring count - auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); - if (!newSponsorSle) - return tefINTERNAL; // LCOV_EXCL_LINE if (auto const ter = setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); !isTesSuccess(ter)) @@ -398,14 +364,31 @@ SponsorshipTransfer::doApply() { auto const newSponsorID = tx.getAccountID(sfSponsor); XRPL_ASSERT(!!newSponsorID, "New sponsor is required when reassigning sponsorship"); + auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); + if (!newSponsorSle) + return tefINTERNAL; // LCOV_EXCL_LINE auto const oldSponsorID = objSle->getAccountID(sponsorField); XRPL_ASSERT(!!oldSponsorID, "Old sponsor is required when reassigning sponsorship"); - - // decrement old sponsor's sponsoring count auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID)); if (!oldSponsorSle) return tefINTERNAL; // LCOV_EXCL_LINE + + // check new sponsor have sufficient balance + // NOLINTNEXTLINE(readability-suspicious-call-argument) + if (auto const ter = checkInsufficientReserve( + ctx_.view(), + ctx_.tx, + sponseeSle, + sponseeSle->getFieldAmount(sfBalance), + newSponsorSle, + ownerCountDelta, + 0, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + + // decrement old sponsor's sponsoring count if (auto const ter = setSponsorFieldU32(oldSponsorSle, sfSponsoringOwnerCount, -ownerCountDelta); !isTesSuccess(ter)) @@ -413,9 +396,6 @@ SponsorshipTransfer::doApply() view().update(oldSponsorSle); // increment new sponsor's sponsoring count - auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); - if (!newSponsorSle) - return tefINTERNAL; // LCOV_EXCL_LINE if (auto const ter = setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta); !isTesSuccess(ter)) @@ -444,6 +424,20 @@ SponsorshipTransfer::doApply() if (!oldSponsorSle) return tefINTERNAL; // LCOV_EXCL_LINE + // The owner takes the reserve burden back when the object is + // no longer sponsored. + if (auto const ter = checkInsufficientReserve( + ctx_.view(), + ctx_.tx, + ownerSle, + balanceBeforeFee(ownerSle), + SLE::pointer(), + ownerCountDelta, + 0, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + // decrement sponsored count if (auto const ter = setSponsorFieldU32(sponseeSle, sfSponsoredOwnerCount, -ownerCountDelta); @@ -473,6 +467,19 @@ SponsorshipTransfer::doApply() auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); if (!newSponsorSle) return tefINTERNAL; // LCOV_EXCL_LINE + + if (auto const ter = checkInsufficientReserve( + ctx_.view(), + ctx_.tx, + sponseeSle, + sponseeSle->getFieldAmount(sfBalance), + newSponsorSle, + 0, + 1, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + if (auto const ter = setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1); !isTesSuccess(ter)) return ter; @@ -490,6 +497,19 @@ SponsorshipTransfer::doApply() auto const newSponsorSle = view().peek(keylet::account(newSponsorID)); if (!newSponsorSle) return tefINTERNAL; // LCOV_EXCL_LINE + + if (auto const ter = checkInsufficientReserve( + ctx_.view(), + ctx_.tx, + sponseeSle, + sponseeSle->getFieldAmount(sfBalance), + newSponsorSle, + 0, + 1, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + if (auto const ter = setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1); !isTesSuccess(ter)) return ter; @@ -513,6 +533,21 @@ SponsorshipTransfer::doApply() { // dissolve account sponsor auto const oldSponsorID = sponseeSle->getAccountID(sfSponsor); + + // The sponsee must be able to hold its own account reserve after + // the sponsorship is removed. + if (auto const ter = checkInsufficientReserve( + ctx_.view(), + ctx_.tx, + sponseeSle, + balanceBeforeFee(sponseeSle), + SLE::pointer(), + 0, + 1, + ctx_.journal); + !isTesSuccess(ter)) + return ter; + sponseeSle->makeFieldAbsent(sfSponsor); view().update(sponseeSle); From bf73446528286229b3aee219e510d28629ca0ede Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 1 Jul 2026 13:44:53 -0400 Subject: [PATCH 16/16] clean up --- src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp | 3 +-- src/libxrpl/tx/transactors/account/SignerListSet.cpp | 3 +-- src/libxrpl/tx/transactors/check/CheckCreate.cpp | 3 +-- src/libxrpl/tx/transactors/delegate/DelegateSet.cpp | 3 +-- src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp | 3 +-- src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp | 3 +-- src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp | 3 +-- .../tx/transactors/lending/LoanBrokerCoverWithdraw.cpp | 9 +++++++-- src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp | 3 +-- src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp | 3 +-- src/libxrpl/tx/transactors/lending/LoanPay.cpp | 3 +-- .../transactors/payment_channel/PaymentChannelFund.cpp | 3 +-- src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp | 3 +-- .../tx/transactors/token/MPTokenIssuanceCreate.cpp | 3 +-- src/libxrpl/tx/transactors/token/TrustSet.cpp | 3 +-- src/libxrpl/tx/transactors/vault/VaultClawback.cpp | 3 +-- 16 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp index 02ba06c29e..b6c6dfb88b 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp @@ -204,8 +204,7 @@ SponsorshipSet::doApply() auto const maxFee = ctx_.tx[~sfMaxFee]; auto const remainingOwnerCount = ctx_.tx[~sfRemainingOwnerCount]; - auto applyViewContext = ctx_.getApplyViewContext(); - auto reserveSponsorAccSle = getTxReserveSponsor(applyViewContext); + auto reserveSponsorAccSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!reserveSponsorAccSle) return reserveSponsorAccSle.error(); // LCOV_EXCL_LINE diff --git a/src/libxrpl/tx/transactors/account/SignerListSet.cpp b/src/libxrpl/tx/transactors/account/SignerListSet.cpp index 8903da47cc..8a24d42c8b 100644 --- a/src/libxrpl/tx/transactors/account/SignerListSet.cpp +++ b/src/libxrpl/tx/transactors/account/SignerListSet.cpp @@ -322,8 +322,7 @@ SignerListSet::replaceSignerList() // We check the reserve against the starting balance because we want to // allow dipping into the reserve to pay fees. This behavior is consistent // with TicketCreate. - auto applyViewContext = ctx_.getApplyViewContext(); - auto const sponsorSle = getTxReserveSponsor(applyViewContext); + auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index 4f9120d3a6..d2281ec3ae 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -195,8 +195,7 @@ CheckCreate::doApply() // A check counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - auto applyViewContext = ctx_.getApplyViewContext(); - auto const sponsorSle = getTxReserveSponsor(applyViewContext); + auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index d8cbed39ad..c506c48223 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -95,8 +95,7 @@ DelegateSet::doApply() if (permissions.empty()) return tecINTERNAL; // LCOV_EXCL_LINE - auto applyViewContext = ctx_.getApplyViewContext(); - auto const sponsorSle = getTxReserveSponsor(applyViewContext); + auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 4ef470a6c1..2b3fd7abdd 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -178,11 +178,10 @@ EscrowCancel::doApply() auto const issuer = amount.getIssuer(); bool const createAsset = account == accountID_; - auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - applyViewContext, + ctx_.getApplyViewContext(), kParityRate, ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep, preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index da1a073afb..3576c0ee78 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -437,8 +437,7 @@ EscrowCreate::doApply() STAmount const amount{ctx_.tx[sfAmount]}; auto const balance = sle->getFieldAmount(sfBalance).xrp(); - auto applyViewContext = ctx_.getApplyViewContext(); - auto const sponsorSle = getTxReserveSponsor(applyViewContext); + auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE // First check: whoever is on the hook for the new owner increment diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 6b22e1b020..5ededceffe 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -362,11 +362,10 @@ EscrowFinish::doApply() : kParityRate; auto const issuer = amount.getIssuer(); bool const createAsset = destID == accountID_; - auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ret = std::visit( [&](T const&) { return escrowUnlockApplyHelper( - applyViewContext, + ctx_.getApplyViewContext(), lockedRate, sled, preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 7f0b282be0..24c762d14f 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -198,9 +198,14 @@ LoanBrokerCoverWithdraw::doApply() associateAsset(*broker, vaultAsset); - auto applyViewContext = ctx_.getApplyViewContext(); return doWithdraw( - applyViewContext, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_); + ctx_.getApplyViewContext(), + accountID_, + dstAcct, + brokerPseudoID, + preFeeBalance_, + amount, + j_); } void diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index 40ae0ed802..bdbd9732a6 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -159,8 +159,7 @@ LoanBrokerDelete::doApply() return ter; } - auto applyViewContext = ctx_.getApplyViewContext(); - if (auto ter = removeEmptyHolding(applyViewContext, brokerPseudoID, vaultAsset, j_)) + if (auto ter = removeEmptyHolding(ctx_.getApplyViewContext(), brokerPseudoID, vaultAsset, j_)) return ter; auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID)); diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index ffe35dd5ea..d97bb54842 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -248,9 +248,8 @@ LoanBrokerSet::doApply() auto& pseudo = *maybePseudo; auto pseudoId = pseudo->at(sfAccount); - auto applyViewContext = ctx_.getApplyViewContext(); if (auto ter = addEmptyHolding( - applyViewContext, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) + ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_)) return ter; // Initialize data fields: diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index 8d2aa466bb..c025da6852 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -624,9 +624,8 @@ LoanPay::doApply() if (brokerPayee == accountID_) { // The broker may have deleted their holding. Recreate it if needed - auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ter = addEmptyHolding( - applyViewContext, + ctx_.getApplyViewContext(), brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset, diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index 305857fc39..a94cdd4fe9 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -90,8 +90,7 @@ PaymentChannelFund::doApply() { // Check reserve and funds availability auto const balance = (*sle)[sfBalance]; - auto applyViewContext = ctx_.getApplyViewContext(); - auto const sponsorSle = getTxReserveSponsor(applyViewContext); + auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE if (auto const ret = diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index b5862cc3b2..59020f6104 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -143,9 +143,8 @@ TER MPTokenAuthorize::doApply() { auto const& tx = ctx_.tx; - auto applyViewContext = ctx_.getApplyViewContext(); return authorizeMPToken( - applyViewContext, + ctx_.getApplyViewContext(), preFeeBalance_, tx[sfMPTokenIssuanceID], accountID_, diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 7574018b7f..f669b1af45 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -197,9 +197,8 @@ TER MPTokenIssuanceCreate::doApply() { auto const& tx = ctx_.tx; - auto applyViewContext = ctx_.getApplyViewContext(); auto const result = create( - applyViewContext, + ctx_.getApplyViewContext(), j_, { .priorBalance = preFeeBalance_, diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index 53085e34ff..3d7bc159b2 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -328,8 +328,7 @@ TrustSet::doApply() // well. A person with no intention of using the gateway // could use the extra XRP for their own purposes. - auto applyViewContext = ctx_.getApplyViewContext(); - auto const sponsorSle = getTxReserveSponsor(applyViewContext); + auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index e77acdc81c..f5ff6d2aa5 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -399,9 +399,8 @@ VaultClawback::doApply() // Keep MPToken if holder is the vault owner. if (holder != vault->at(sfOwner)) { - auto applyViewContext = ctx_.getApplyViewContext(); if (auto const ter = - removeEmptyHolding(applyViewContext, holder, sharesDestroyed.asset(), j_); + removeEmptyHolding(ctx_.getApplyViewContext(), holder, sharesDestroyed.asset(), j_); isTesSuccess(ter)) { JLOG(j_.debug()) //