Merge branch 'xrplf/sponsor' into mvadari/sponsor/reserve-context

This commit is contained in:
Mayukha Vadari
2026-07-02 17:10:18 -04:00
committed by GitHub
4 changed files with 91 additions and 1 deletions

View File

@@ -197,6 +197,15 @@ getLedgerEntryOwnerCount(T const& sle)
// Vaults require 2 owner counts (the vault and a pseudo-account)
case ltVAULT:
return 2;
case ltSIGNER_LIST: {
// Mirror SignerListSet's owner-count accounting so that create and
// delete agree. Modern lists (post-MultiSignReserve) carry the
// lsfOneOwnerCount flag and cost a single owner count. Legacy
// pre-MultiSignReserve lists cost 2 + signer_count owner counts
if (sle->isFlag(lsfOneOwnerCount))
return 1;
return 2 + static_cast<std::uint32_t>(sle->getFieldArray(sfSignerEntries).size());
}
default:
return 1;
}

View File

@@ -69,6 +69,15 @@ SponsorshipOwnerCountsMatch::visitEntry(
return 0;
return 2;
}
case ltSIGNER_LIST: {
if (!sle->isFieldPresent(sfSponsor))
return 0;
// Modern lists carry lsfOneOwnerCount and cost 1.
// Legacy (pre-MultiSignReserve) lists cost 2 + signer_count.
if (sle->isFlag(lsfOneOwnerCount))
return 1;
return 2 + static_cast<std::uint32_t>(sle->getFieldArray(sfSignerEntries).size());
}
default: {
if (sle->isFieldPresent(sfSponsor))
return 1;

View File

@@ -330,7 +330,7 @@ SponsorshipTransfer::doApply()
if (!ownerSle)
return tefINTERNAL; // LCOV_EXCL_LINE
std::int64_t const ownerCountDelta = 1;
auto const ownerCountDelta = static_cast<std::int32_t>(getLedgerEntryOwnerCount(objSle));
auto const& sponsorField = getLedgerEntrySponsorField(objSle, *ownerID);

View File

@@ -3438,6 +3438,77 @@ public:
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
// Legacy (pre-MultiSignReserve) SignerLists lack lsfOneOwnerCount and cost
// 2 + signer_count owner units, whereas modern lists cost 1.
void
testLegacySignerListReserve()
{
testcase("Legacy SignerList sponsorship reserve");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const carol("carol");
Account const dave("dave");
Account const sponsor("sponsor");
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, carol, dave, sponsor);
env.close();
// Modern 3-signer list: weight 1, lsfOneOwnerCount set.
env(signers(alice, 1, {{bob, 1}, {carol, 1}, {dave, 1}}));
env.close();
auto const signerListKeylet = keylet::signerList(alice.id());
auto const sponsorKeylet = keylet::sponsorship(sponsor.id(), alice.id());
std::uint32_t const legacyWeight = 5; // 2 + 3 signers
BEAST_EXPECT(ownerCount(env, alice) == 1);
// Pre-fund exactly the legacy weight
env(sponsor::set_reserve(sponsor, 0, legacyWeight), sponsor::SponseeAcc(alice));
env.close();
if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
BEAST_EXPECT(sle->getFieldU32(sfRemainingOwnerCount) == legacyWeight);
// Synthesize a pre-MultiSignReserve list: clear lsfOneOwnerCount and
// restore the owner's OwnerCount to the legacy weight.
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal) -> bool {
auto signerList = std::make_shared<SLE>(*view.read(signerListKeylet));
auto account = std::make_shared<SLE>(*view.read(keylet::account(alice.id())));
signerList->clearFlag(lsfOneOwnerCount);
account->setFieldU32(sfOwnerCount, legacyWeight);
view.rawReplace(signerList);
view.rawReplace(account);
return true;
});
if (auto const sle = env.le(signerListKeylet); BEAST_EXPECT(sle))
BEAST_EXPECT((sle->getFlags() & lsfOneOwnerCount) == 0);
BEAST_EXPECT(ownerCount(env, alice) == legacyWeight);
// Create must charge the full legacy weight (5), not 1: the bug bumped
// the counters by 1 and left 4 pre-funded units unspent.
env(sponsor::transfer(alice, tfSponsorshipCreate, signerListKeylet.key),
sponsor::As(sponsor, spfSponsorReserve));
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == legacyWeight);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == legacyWeight);
// All pre-funded units consumed (drained to absent).
if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
// Delete unwinds the legacy weight; create bumped by the same amount, so
// the counters return to 0 (the bug bumped by 1 -> underflow on delete).
env(signers(alice, NoneT()));
BEAST_EXPECT(!env.le(signerListKeylet));
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
}
void
testSponsoredTrustLineNoFreeReserve()
{
@@ -4346,6 +4417,7 @@ protected:
testSponsoredFreeTierReserve();
testTransferSponsor();
testLegacySignerListReserve();
testSponsorFee();
testSponsorAccount();