From 4a91351bcdc13ce67f426300b7b0a79dcd4ffbf8 Mon Sep 17 00:00:00 2001 From: tequ Date: Sun, 22 Feb 2026 17:21:04 +0900 Subject: [PATCH] Add `sfSponsee` to `ttSponsorshipTransfer` --- .../xrpl/protocol/detail/transactions.macro | 1 + .../Sponsor/SponsorshipTransfer.cpp | 64 ++++++++++----- src/test/app/Sponsor_test.cpp | 77 ++++++++++++++++++- 3 files changed, 120 insertions(+), 22 deletions(-) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 8231a72319..355290774a 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -1068,6 +1068,7 @@ TRANSACTION(ttSPONSORSHIP_TRANSFER, 85, SponsorshipTransfer, noPriv, ({ {sfObjectID, soeOPTIONAL}, + {sfSponsee, soeOPTIONAL}, })) /** This transaction create sponsorship object */ diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp index 449f40b9c9..8795586ae0 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp @@ -36,6 +36,11 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx) JLOG(ctx.j.debug()) << "preflight: tfSponsorReserve should not be set when creating sponsorship"; return temINVALID_FLAG; } + if (ctx.tx.isFieldPresent(sfSponsee)) + { + JLOG(ctx.j.debug()) << "preflight: sfSponsee should be available only when ending sponsorship"; + return temMALFORMED; + } } if (flags & tfSponsorshipReassign) { @@ -44,6 +49,11 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx) JLOG(ctx.j.debug()) << "preflight: tfSponsorReserve should be set when reassigning sponsorship"; return temINVALID_FLAG; } + if (ctx.tx.isFieldPresent(sfSponsee)) + { + JLOG(ctx.j.debug()) << "preflight: sfSponsee should not be set when reassigning sponsorship"; + return temMALFORMED; + } } if (flags & tfSponsorshipEnd) { @@ -52,6 +62,15 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx) JLOG(ctx.j.debug()) << "preflight: tfSponsorReserve should not be set when ending sponsorship"; return temINVALID_FLAG; } + + if (ctx.tx.isFieldPresent(sfSponsee)) + { + if (ctx.tx.getAccountID(sfSponsee) == ctx.tx.getAccountID(sfAccount)) + { + JLOG(ctx.j.debug()) << "preflight: sfSponsee should not be the same as the account"; + return temMALFORMED; + } + } } // When an account sponsoring, sfSponsorSignature must be provided @@ -202,8 +221,9 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) auto const account = ctx.tx[sfAccount]; - auto const accSle = ctx.view.read(keylet::account(account)); - if (!accSle) + auto const sponsee = ctx.tx[~sfSponsee].value_or(account); + auto const sponseeSle = ctx.view.read(keylet::account(sponsee)); + if (!sponseeSle) return tecINTERNAL; // LCOV_EXCL_LINE if (isObjectSponsor) @@ -214,8 +234,8 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) auto const ownerCountDelta = getLedgerEntryOwnerCount(sle); - auto const owner = getLedgerEntryOwner(ctx.view, sle, account); - if (!owner || owner != account) + auto const owner = getLedgerEntryOwner(ctx.view, sle, sponsee); + if (!owner || owner != sponsee) return tecNO_PERMISSION; auto const& sponsorField = getLedgerEntrySponsorField(sle, *owner); @@ -250,7 +270,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) // check new sponsor have sufficient balance if (auto const ter = checkInsufficientReserve( - ctx.view, ctx.tx, accSle, accSle->getFieldAmount(sfBalance), newSponsor, ownerCountDelta); + ctx.view, ctx.tx, sponseeSle, sponseeSle->getFieldAmount(sfBalance), newSponsor, ownerCountDelta); !isTesSuccess(ter)) return ter; } @@ -262,7 +282,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; // check account is not sponsored yet - if (accSle->isFieldPresent(sfSponsor)) + if (sponseeSle->isFieldPresent(sfSponsor)) return tecNO_PERMISSION; } else if (flags & tfSponsorshipReassign) @@ -271,7 +291,7 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; // check account is already sponsored - if (!accSle->isFieldPresent(sfSponsor)) + if (!sponseeSle->isFieldPresent(sfSponsor)) return tecNO_PERMISSION; } else if (flags & tfSponsorshipEnd) @@ -280,15 +300,15 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; // check account is sponsored - if (!accSle->isFieldPresent(sfSponsor)) + if (!sponseeSle->isFieldPresent(sfSponsor)) 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. - if (auto const ter = - checkInsufficientReserve(ctx.view, ctx.tx, accSle, accSle->getFieldAmount(sfBalance), newSponsor, 0, 1); + if (auto const ter = checkInsufficientReserve( + ctx.view, ctx.tx, sponseeSle, sponseeSle->getFieldAmount(sfBalance), newSponsor, 0, 1); !isTesSuccess(ter)) return ter; } @@ -330,8 +350,9 @@ SponsorshipTransfer::doApply() auto const flags = tx.getFlags(); bool const isObjectSponsor = index != std::nullopt; - auto const accSle = view().peek(keylet::account(account_)); - if (!accSle) + auto const sponsee = tx[~sfSponsee].value_or(account_); + auto const sponseeSle = view().peek(keylet::account(sponsee)); + if (!sponseeSle) return tefINTERNAL; // LCOV_EXCL_LINE auto const setSponsorFieldU32 = [](auto const& sle, auto const& field, auto const& delta) { @@ -441,8 +462,8 @@ SponsorshipTransfer::doApply() return tefINTERNAL; // LCOV_EXCL_LINE // decrement sponsored count - setSponsorFieldU32(accSle, sfSponsoredOwnerCount, -ownerCountDelta); - view().update(accSle); + setSponsorFieldU32(sponseeSle, sfSponsoredOwnerCount, -ownerCountDelta); + view().update(sponseeSle); // decrement old sponsoring count setSponsorFieldU32(oldSponsorSle, sfSponsoringOwnerCount, -ownerCountDelta); @@ -473,8 +494,8 @@ SponsorshipTransfer::doApply() view().update(newSponsorSle); // set new sponsor to account - accSle->setAccountID(sfSponsor, newSponsor); - view().update(accSle); + sponseeSle->setAccountID(sfSponsor, newSponsor); + view().update(sponseeSle); } else if (flags & tfSponsorshipReassign) { @@ -486,7 +507,7 @@ SponsorshipTransfer::doApply() view().update(newSponsorSle); // decrement old sponsoring count - auto const oldSponsor = accSle->getAccountID(sfSponsor); + auto const oldSponsor = sponseeSle->getAccountID(sfSponsor); auto const oldSponsorSle = view().peek(keylet::account(oldSponsor)); if (!oldSponsorSle) return tefINTERNAL; // LCOV_EXCL_LINE @@ -494,14 +515,15 @@ SponsorshipTransfer::doApply() view().update(oldSponsorSle); // set new sponsor to account - accSle->setAccountID(sfSponsor, newSponsor); - view().update(accSle); + sponseeSle->setAccountID(sfSponsor, newSponsor); + view().update(sponseeSle); } else if (flags & tfSponsorshipEnd) { // dissolve account sponsor - auto const oldSponsor = accSle->getAccountID(sfSponsor); - accSle->makeFieldAbsent(sfSponsor); + auto const oldSponsor = sponseeSle->getAccountID(sfSponsor); + sponseeSle->makeFieldAbsent(sfSponsor); + view().update(sponseeSle); // decrement account sponsoring count auto const oldSponsorSle = view().peek(keylet::account(oldSponsor)); diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index d4a4102050..d2720a796e 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -549,7 +549,7 @@ public: using namespace test::jtx; { - // invalid flags + // invalid fields Env env{*this, testable_amendments()}; Account const alice("alice"); Account const bob("bob"); @@ -573,16 +573,28 @@ public: // invalid tfSponsorshipCreate // no sponsor field present env(sponsor::transfer(alice, tfSponsorshipCreate), ter(temINVALID_FLAG)); + // sponsee field present + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::sponseeAcc(bob), + sponsor::as(sponsor1, tfSponsorReserve), + ter(temMALFORMED)); // invalid tfSponsorshipReassign // no sponsor field present env(sponsor::transfer(alice, tfSponsorshipReassign), ter(temINVALID_FLAG)); + // sponsee field present + env(sponsor::transfer(alice, tfSponsorshipReassign), + sponsor::sponseeAcc(bob), + sponsor::as(sponsor1, tfSponsorReserve), + ter(temMALFORMED)); // invalid tfSponsorshipEnd // sponsor field present env(sponsor::transfer(alice, tfSponsorshipEnd), sponsor::as(sponsor1, tfSponsorReserve), ter(temINVALID_FLAG)); + // account = sponsee + env(sponsor::transfer(alice, tfSponsorshipEnd), sponsor::sponseeAcc(alice), ter(temMALFORMED)); } { @@ -705,6 +717,30 @@ public: env(sponsor::transfer(bob, tfSponsorshipEnd), ter(tecNO_PERMISSION)); env.close(); } + { + // dissolve account sponsorship from sponsor + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + env(sponsor::transfer(alice, tfSponsorshipCreate), + sponsor::as(sponsor, tfSponsorReserve), + sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(alice)->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd), sponsor::sponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfSponsor)); + BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0); + } + { // sponsor object (co-signing) Env env{*this, testable_amendments()}; @@ -951,6 +987,45 @@ public: BEAST_EXPECT(sponsor2Sle->getFieldU32(sfReserveCount) == 100); // paybacked } + { + // Dissolve object sponsorship from sponsor + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, bob, sponsor); + env.close(); + + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1))); + env.close(); + + auto const checkId = keylet::check(alice, seq).key; + BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + + env(sponsor::transfer(alice, tfSponsorshipCreate, checkId), + sponsor::as(sponsor, tfSponsorReserve), + sig(sfSponsorSignature, sponsor)); + env.close(); + + BEAST_EXPECT(env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // not the owner of the object + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), ter(tecNO_PERMISSION)); + env.close(); + + env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::sponseeAcc(alice)); + env.close(); + + BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor)); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + { // sponsor trustline Account const alice("alice");