Create Sponsored Account / AccountDelete with ltSponsorship, Sponsored Account

This commit is contained in:
tequ
2025-09-13 22:34:54 +09:00
parent 8e895a3e7d
commit 4eea76ca92
5 changed files with 161 additions and 37 deletions

View File

@@ -113,8 +113,9 @@ constexpr std::uint32_t tfOfferCreateMask =
constexpr std::uint32_t tfNoRippleDirect = 0x00010000;
constexpr std::uint32_t tfPartialPayment = 0x00020000;
constexpr std::uint32_t tfLimitQuality = 0x00040000;
constexpr std::uint32_t tfSponsorCreatedAccount = 0x00080000;
constexpr std::uint32_t tfPaymentMask =
~(tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect);
~(tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect | tfSponsorCreatedAccount);
constexpr std::uint32_t tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
// TrustSet flags:

View File

@@ -527,16 +527,48 @@ public:
Account const alice("alice");
Account const sponsor("sponsor");
Account const bob("bob");
Account const charlie("charlie");
env.fund(XRP(10000), alice, sponsor);
Account const bob("bob");
env(pay(alice, bob, STAmount(env.current()->fees().accountReserve(0))),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
// Account is not sponsored by normal Sponsor specification
{
env(pay(alice,
bob,
STAmount(env.current()->fees().accountReserve(0))),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1);
auto const bobSle = env.le(keylet::account(bob));
BEAST_EXPECT(!bobSle->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
}
// Use tfSponsorCreatedAccount to sponsor an account
{
// to funded accoutn
env(pay(sponsor,
bob,
STAmount(env.current()->fees().accountReserve(0))),
txflags(tfSponsorCreatedAccount),
ter(tecNO_SPONSOR_PERMISSION));
// to non-funded account
env(pay(sponsor,
charlie,
STAmount(env.current()->fees().accountReserve(0))),
txflags(tfSponsorCreatedAccount));
env.close();
auto const charlieSle = env.le(keylet::account(charlie));
BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsorAccount));
BEAST_EXPECT(
charlieSle->getAccountID(sfSponsorAccount) == sponsor.id());
BEAST_EXPECT(sponsoredOwnerCount(env, charlie) == 0);
BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1);
}
}
void
@@ -926,25 +958,66 @@ public:
{
testcase("AccountDelete");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
{
// Delete Account with ltSponsorship
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
// set sponsor
env(sponsor::set(sponsor, alice, 0, 100, XRP(100)), ter(tesSUCCESS));
env.close();
// set sponsor
env(sponsor::set(sponsor, alice, 0, 100, XRP(100)),
ter(tesSUCCESS));
env.close();
// AccountDelete
env(acctdelete(alice, bob));
env.close();
incLgrSeqForAccDel(env, alice);
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
auto const keylet = keylet::sponsor(sponsor, alice);
auto const sponsorObj = env.le(keylet);
BEAST_EXPECT(sponsorObj);
// AccountDelete
auto const requiredFee = drops(env.current()->fees().increment);
env(acctdelete(alice, bob), fee(requiredFee), ter(tesSUCCESS));
env.close();
}
{
// Delete SponsoredAccount
Env env{*this, testable_amendments()};
env.memoize(alice);
env.fund(XRP(1000000), bob, sponsor);
env.close();
// create SponsoredAccount
env(pay(sponsor, alice, XRP(10000)),
txflags(tfSponsorCreatedAccount));
env.close();
incLgrSeqForAccDel(env, alice);
// AccountDelete: destination = non-sponsor
auto const requiredFee = drops(env.current()->fees().increment);
env(acctdelete(alice, bob),
fee(requiredFee),
ter(tecNO_SPONSOR_PERMISSION));
auto const sponsorSle = env.le(keylet::account(sponsor));
BEAST_EXPECT(
sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1);
incLgrSeqForAccDel(env, alice);
// AccountDelete: destination = sponsor
env(acctdelete(alice, sponsor), fee(requiredFee), ter(tesSUCCESS));
auto const sponsorSle2 = env.le(keylet::account(sponsor));
BEAST_EXPECT(
!sponsorSle2->isFieldPresent(sfSponsoringAccountCount));
}
}
void
@@ -986,7 +1059,7 @@ public:
testSponsorReserve();
testDisallowIncoming();
// testAccountDelete();
testAccountDelete();
}
};

View File

@@ -299,6 +299,15 @@ DeleteAccount::preclaim(PreclaimContext const& ctx)
return tecHAS_OBLIGATIONS;
}
if (sleAccount->isFieldPresent(sfSponsorAccount))
{
if (dst != sleAccount->getAccountID(sfSponsorAccount))
return tecNO_SPONSOR_PERMISSION;
}
if (sleAccount->isFieldPresent(sfSponsoringOwnerCount) ||
sleAccount->isFieldPresent(sfSponsoringAccountCount))
return tecHAS_OBLIGATIONS;
// We don't allow an account to be deleted if its sequence number
// is within 256 of the current ledger. This prevents replay of old
// transactions if this account is resurrected after it is deleted.
@@ -430,6 +439,22 @@ DeleteAccount::doApply()
(*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
ctx_.deliver(mSourceBalance);
if (src->isFieldPresent(sfSponsorAccount))
{
auto const sponsorAcc = src->getAccountID(sfSponsorAccount);
auto sponsorSle = view().peek(keylet::account(sponsorAcc));
auto const sponsoringAccountCount =
sponsorSle->getFieldU32(sfSponsoringAccountCount);
if (sponsoringAccountCount == 1)
sponsorSle->makeFieldAbsent(sfSponsoringAccountCount);
else
sponsorSle->setFieldU32(
sfSponsoringAccountCount, sponsoringAccountCount - 1);
view().update(sponsorSle);
}
XRPL_ASSERT(
(*src)[sfBalance] == XRPAmount(0),
"ripple::DeleteAccount::doApply : source balance is zero");

View File

@@ -98,6 +98,15 @@ Payment::preflight(PreflightContext const& ctx)
return temINVALID_FLAG;
}
if (txFlags & tfSponsorCreatedAccount)
{
if (!ctx.rules.enabled(featureSponsor))
return temINVALID_FLAG;
if (!dstAmount.native())
return temMALFORMED;
}
if (mptDirect && ctx.tx.isFieldPresent(sfPaths))
return temMALFORMED;
@@ -330,19 +339,24 @@ Payment::preclaim(PreclaimContext const& ctx)
return tecNO_DST_INSUF_XRP;
}
}
else if (
(sleDst->getFlags() & lsfRequireDestTag) &&
!ctx.tx.isFieldPresent(sfDestinationTag))
else
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
if (txFlags & tfSponsorCreatedAccount)
return tecNO_SPONSOR_PERMISSION;
// We didn't make this test for a newly-formed account because there's
// no way for this field to be set.
JLOG(ctx.j.trace())
<< "Malformed transaction: DestinationTag required.";
if ((sleDst->getFlags() & lsfRequireDestTag) &&
!ctx.tx.isFieldPresent(sfDestinationTag))
{
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
return tecDST_TAG_NEEDED;
// We didn't make this test for a newly-formed account because
// there's no way for this field to be set.
JLOG(ctx.j.trace())
<< "Malformed transaction: DestinationTag required.";
return tecDST_TAG_NEEDED;
}
}
// Payment with at least one intermediate step and uses transitive balances.
@@ -415,12 +429,19 @@ Payment::doApply()
sleDst->setAccountID(sfAccount, dstAccountID);
sleDst->setFieldU32(sfSequence, seqno);
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (sponsor.has_value())
if (txFlags & tfSponsorCreatedAccount)
{
auto const sponsor = view().peek(keylet::account(account_));
if (!sponsor)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const currentSponsoringAccountCount =
sponsor->getFieldU32(sfSponsoringAccountCount);
sponsor->setFieldU32(
sfSponsoringAccountCount, currentSponsoringAccountCount + 1);
view().update(sponsor);
addSponsorToLedgerEntry(sleDst, sponsor);
sponsor.value()->setFieldU32(sfSponsoringAccountCount, 1);
view().update(sponsor.value());
view().update(sponsor);
}
view().insert(sleDst);

View File

@@ -276,7 +276,7 @@ SponsorshipSet::deleteSponsorship(
std::shared_ptr<SLE> const& sle,
beast::Journal j)
{
auto const sponsor = sle->getAccountID(sfSponsorAccount);
auto const sponsor = sle->getAccountID(sfAccount);
auto const sponsee = sle->getAccountID(sfSponsee);
// adjust balance
@@ -285,9 +285,13 @@ SponsorshipSet::deleteSponsorship(
return tecINTERNAL;
auto const feeAmount = sle->getFieldAmount(sfFeeAmount);
(*sponsorAccSle)[sfBalance] += feeAmount;
auto const reserveSponsor = getLedgerEntryReserveSponsor(view, sle);
adjustOwnerCount(view, sponsorAccSle, reserveSponsor, -1, j);
view.update(sponsorAccSle);
// delete sponsor node
view.dirRemove(
keylet::ownerDir(sponsor), (*sle)[sfSponsorNode], sle->key(), false);