From 6bd2e911eb53944200b0c2cbbe47e63deeddcd2c Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 10 Apr 2026 17:07:36 +0900 Subject: [PATCH] fix Wrong Account in Sponsorship Keylet Lookup Causes Reserve Count Leakage --- .../Sponsor/SponsorshipTransfer.cpp | 20 ++++---- src/test/app/Sponsor_test.cpp | 49 ++++++++++++++++++- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp index b84c840322..50bdb5aac0 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp @@ -448,8 +448,8 @@ SponsorshipTransfer::doApply() if (!hasSignature) { // use ReserveCount for pre-funded sponsoring - if (auto const ter = - adjustReserveCount(view(), account_, newSponsorAccountID, -ownerCountDelta); + if (auto const ter = adjustReserveCount( + view(), sponseeAccountID, newSponsorAccountID, -ownerCountDelta); !isTesSuccess(ter)) return ter; } @@ -485,19 +485,19 @@ SponsorshipTransfer::doApply() if (!hasSignature) { // use ReserveCount for pre-funded sponsoring - if (auto const ter = - adjustReserveCount(view(), account_, newSponsorAccountID, -ownerCountDelta); + if (auto const ter = adjustReserveCount( + view(), sponseeAccountID, newSponsorAccountID, -ownerCountDelta); !isTesSuccess(ter)) return ter; } // payback the reserve count if ltSponsorship exists if (auto const sponsorSle = - view().exists(keylet::sponsor(oldSponsorAccountID, account_)); + view().exists(keylet::sponsor(oldSponsorAccountID, sponseeAccountID)); sponsorSle) { - if (auto const ter = - adjustReserveCount(view(), account_, oldSponsorAccountID, ownerCountDelta); + if (auto const ter = adjustReserveCount( + view(), sponseeAccountID, oldSponsorAccountID, ownerCountDelta); !isTesSuccess(ter)) return ter; } @@ -521,11 +521,11 @@ SponsorshipTransfer::doApply() // payback the reserve count if ltSponsorship exists if (auto const sponsorSle = - view().exists(keylet::sponsor(oldSponsorAccountID, account_)); + view().exists(keylet::sponsor(oldSponsorAccountID, sponseeAccountID)); sponsorSle) { - if (auto const ter = - adjustReserveCount(view(), account_, oldSponsorAccountID, ownerCountDelta); + if (auto const ter = adjustReserveCount( + view(), sponseeAccountID, oldSponsorAccountID, ownerCountDelta); !isTesSuccess(ter)) return ter; } diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index c875aa2752..3cb5d92040 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -1168,7 +1168,7 @@ public: } { - // Dissolve object sponsorship from sponsor + // Dissolve object sponsorship from sponsor(no-ltSponsorship) Env env{*this, testable_amendments()}; Account const alice("alice"); Account const bob("bob"); @@ -1207,6 +1207,53 @@ public: BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); } + { + // Dissolve object sponsorship from sponsor (with ltSponsorship) + 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, spfSponsorReserve), + sig(sfSponsorSignature, sponsor)); + env.close(); + + env(sponsor::set_reserve(sponsor, 0, 100), sponsor::sponseeAcc(alice)); + 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); + BEAST_EXPECT( + env.le(keylet::sponsor(sponsor, alice))->getFieldU32(sfReserveCount) == 100); + + // 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); + BEAST_EXPECT( + env.le(keylet::sponsor(sponsor, alice))->getFieldU32(sfReserveCount) == 101); + } + { // sponsor trustline Account const alice("alice");