Fix sponsor reserve accounting edge cases (#7575)

This commit is contained in:
yinyiqian1
2026-06-22 15:18:22 -04:00
committed by GitHub
parent 4a3abc0407
commit 018e736a3b
6 changed files with 83 additions and 2 deletions

View File

@@ -1048,6 +1048,10 @@ ValidVault::finalize(
// TBD
return true;
}
case ttSPONSORSHIP_TRANSFER: {
// SponsorshipTransfer may update a vault's sfSponsor
return true;
}
default:
// LCOV_EXCL_START

View File

@@ -205,6 +205,9 @@ getLedgerEntryOwnerCount(T const& sle)
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;
}

View File

@@ -132,6 +132,9 @@ Payment::preflight(PreflightContext const& ctx)
if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality))
return temINVALID_FLAG;
if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths))
return temINVALID;
if (!dstAmount.native())
return temBAD_AMOUNT;
}

View File

@@ -20,8 +20,10 @@
#include <test/jtx/multisign.h>
#include <test/jtx/noop.h>
#include <test/jtx/offer.h>
#include <test/jtx/paths.h>
#include <test/jtx/pay.h>
#include <test/jtx/permissioned_domains.h>
#include <test/jtx/sendmax.h>
#include <test/jtx/seq.h>
#include <test/jtx/sig.h>
#include <test/jtx/sponsor.h>
@@ -1856,6 +1858,20 @@ public:
env(pay(alice, bob, usd(100)), Txflags(tfSponsorCreatedAccount), Ter(temBAD_AMOUNT));
env.close();
// Sponsored account creation is reserve sponsorship and is only supported for direct XRP
// payments.
env(pay(alice, bob, drops(1)),
Txflags(tfSponsorCreatedAccount),
Sendmax(usd(2)),
Ter(temINVALID));
env.close();
env(pay(alice, bob, drops(1)),
Txflags(tfSponsorCreatedAccount),
Path(~XRP),
Ter(temINVALID));
env.close();
// Account is not sponsored by normal Sponsor specification
{
env(pay(alice, bob, drops(baseAccountReserve(*env.current(), 0))),
@@ -4994,6 +5010,61 @@ public:
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
// SponsorshipTransfer
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault const vault{env};
auto const [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
// Alice owns the vault, pseudo account and MPToken
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
if (cosigning)
{
// Alice lets sponsor to sponsor her Vault
env(sponsor::transfer(alice, tfSponsorshipCreate, vaultKeylet.key),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
}
else
{
// Create sponsorship with reserve count being 2 (for vault and pseudo account)
env(sponsor::set_reserve(sponsor, 0, 2), sponsor::SponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, tfSponsorshipCreate, vaultKeylet.key),
sponsor::As(sponsor, spfSponsorReserve));
env.close();
auto const sponsorshipSle = env.le(keylet::sponsor(sponsor, alice));
if (!BEAST_EXPECT(sponsorshipSle))
return;
BEAST_EXPECT(sponsorshipSle->getFieldU32(sfReserveCount) == 0);
}
BEAST_EXPECT(env.le(vaultKeylet)->getAccountID(sfSponsor) == sponsor.id());
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
// Vault counts for 2 reserves, vault and the pseudo account
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
// End sponsorship
env(sponsor::transfer(alice, tfSponsorshipEnd, vaultKeylet.key));
env.close();
BEAST_EXPECT(!env.le(vaultKeylet)->isFieldPresent(sfSponsor));
BEAST_EXPECT(ownerCount(env, alice) == 3);
// Sponsorship ended and the sponsored owner count should be 0.
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
}
void