From 9264136b36ef9e248f28cf65016bd08975938c32 Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 10 Apr 2026 17:41:33 +0900 Subject: [PATCH] fix deleteAMMTrustLine Reads Sponsor Fields After They Are Erased --- .../ledger/helpers/RippleStateHelpers.cpp | 6 +- src/test/app/Sponsor_test.cpp | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index cc01220738..c5730e46d0 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -781,6 +781,9 @@ deleteAMMTrustLine( if (ammAccountID && (low != *ammAccountID && high != *ammAccountID)) return terNO_AMM; + auto const sponsorSle = + getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor); + if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter)) { JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline."; @@ -791,10 +794,7 @@ deleteAMMTrustLine( if ((sleState->getFlags() & uFlags) == 0u) return tecINTERNAL; // LCOV_EXCL_LINE - auto const sponsorSle = - getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor); adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, -1, j); - removeSponsorFromLedgerEntry(sleState, !ammLow ? sfLowSponsor : sfHighSponsor); return tesSUCCESS; } diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 3cb5d92040..b48c81660e 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -2216,6 +2216,63 @@ public: BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); } } + { + // AMMDelete + // - remove sponsored LPToken trustlines + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.reference_fee = XRPAmount(1); + return cfg; + }), + testable_amendments()); + env.fund(XRP(20'000), alice, gw, sponsor); + env.close(); + env(trust(alice, USD(10'000))); + env.close(); + env(pay(gw, alice, USD(10'000))); + env.close(); + + AMM amm(env, gw, XRP(10'000), USD(10'000)); + for (auto i = 0; i < (maxDeletableAMMTrustLines * 2) + 10; ++i) + { + Account const a{std::to_string(i)}; + env.fund(XRP(1'000), a); + if (cosigning) + { + env(trust(a, STAmount{amm.lptIssue(), 10'000}), + sponsor::as(sponsor, spfSponsorReserve), + sig(sfSponsorSignature, sponsor)); + env.close(); + } + else + { + env(sponsor::set_reserve(sponsor, 0, 1), sponsor::sponseeAcc(a)); + env.close(); + env(trust(a, STAmount{amm.lptIssue(), 10'000}), + sponsor::as(sponsor, spfSponsorReserve)); + env.close(); + } + } + + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == maxDeletableAMMTrustLines * 2 + 10); + + // The trustlines are partially deleted. + amm.withdrawAll(gw); + BEAST_EXPECT(amm.ammExists()); + + // AMMDelete has to be called twice to delete AMM. + amm.ammDelete(alice, ter(tecINCOMPLETE)); + BEAST_EXPECT(amm.ammExists()); + + // Deletes remaining trustlines and deletes AMM. + amm.ammDelete(alice); + BEAST_EXPECT(!amm.ammExists()); + BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount()))); + + BEAST_EXPECT( + !env.le(keylet::account(sponsor))->isFieldPresent(sfSponsoringAccountCount)); + } } void