From 5e65813a52b23f2d431248fa236dcb07dfc62b81 Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 1 Aug 2025 18:21:44 +0900 Subject: [PATCH] add reserve checks for SponsorTransfer --- include/xrpl/protocol/Fees.h | 1 - src/test/app/Sponsor_test.cpp | 75 ++++++++++++++++++++- src/xrpld/app/tx/detail/SponsorTransfer.cpp | 71 ++++++++++++++----- src/xrpld/ledger/View.h | 3 +- src/xrpld/ledger/detail/View.cpp | 12 ++-- 5 files changed, 137 insertions(+), 25 deletions(-) diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 937915c8f6..f488c93428 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -53,7 +53,6 @@ struct Fees bool isAccountSponsored = false, std::size_t sponsoringAccountCount = 0) const { - // return reserve + ownerCount * increment; return (isAccountSponsored ? XRPAmount(0) : reserve) + increment * (ownerCount + sponsoringOwnerCount - sponsoredOwnerCount) + diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 1578388b6e..8f789f5755 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -63,7 +63,17 @@ public: Account const alice("alice"); Account const sponsor1("sponsor1"); Account const sponsor2("sponsor2"); - env.fund(XRP(10000), alice, sponsor1, sponsor2); + env.fund(XRP(10000), alice); + env.fund(env.current()->fees().reserve * 2 - 1, sponsor1, sponsor2); + env.close(); + + env(sponsor::transfer(alice), + sponsor::as(sponsor1, tfSponsorReserve), + sponsor::sig(sponsor1), + ter(tecINSUFFICIENT_RESERVE)); + + env(pay(alice, sponsor1, drops(1))); + env.close(); env(sponsor::transfer(alice), sponsor::as(sponsor1, tfSponsorReserve), @@ -81,6 +91,14 @@ public: BEAST_EXPECT(sle1->getAccountID(sfSponsorAccount) == sponsor1.id()); // transfer sponsor + env(sponsor::transfer(alice), + sponsor::as(sponsor2, tfSponsorReserve), + sponsor::sig(sponsor2), + ter(tecINSUFFICIENT_RESERVE)); + + env(pay(alice, sponsor2, drops(1))); + env.close(); + env(sponsor::transfer(alice), sponsor::as(sponsor2, tfSponsorReserve), sponsor::sig(sponsor2)); @@ -100,6 +118,21 @@ public: BEAST_EXPECT(sle2->getAccountID(sfSponsorAccount) == sponsor2.id()); // dissolve sponsor + env(pay(alice, + sponsor2, + (env.balance(alice).value() - + env.current()->fees().reserve - XRP(1) + drops(1))), + fee(XRP(1))); + env.close(); + + env.require( + balance(alice, env.current()->fees().reserve - drops(1))); + env(sponsor::transfer(alice), ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(pay(sponsor2, alice, XRP(1))); + env.close(); + env(sponsor::transfer(alice)); env.close(); @@ -122,7 +155,13 @@ public: Account const bob("bob"); Account const sponsor1("sponsor1"); Account const sponsor2("sponsor2"); - env.fund(XRP(10000), alice, bob, sponsor1, sponsor2); + env.fund(XRP(10000), alice, bob); + env.fund( + env.current()->fees().reserve + + env.current()->fees().increment - drops(1), + sponsor1, + sponsor2); + env.close(); auto const seq = env.seq(alice); env(check::create(alice, bob, XRP(1))); @@ -131,6 +170,15 @@ public: auto const checkId = keylet::check(alice, seq).key; BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr); + env(sponsor::transfer(alice, checkId), + sponsor::as(sponsor1, tfSponsorReserve), + sponsor::sig(sponsor1), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(pay(alice, sponsor1, drops(1))); + env.close(); + env(sponsor::transfer(alice, checkId), sponsor::as(sponsor1, tfSponsorReserve), sponsor::sig(sponsor1)); @@ -148,6 +196,14 @@ public: BEAST_EXPECT(sle1->getAccountID(sfSponsorAccount) == sponsor1.id()); // transfer sponsor + env(sponsor::transfer(alice, checkId), + sponsor::as(sponsor2, tfSponsorReserve), + sponsor::sig(sponsor2), + ter(tecINSUFFICIENT_RESERVE)); + + env(pay(alice, sponsor2, drops(1))); + env.close(); + env(sponsor::transfer(alice, checkId), sponsor::as(sponsor2, tfSponsorReserve), sponsor::sig(sponsor2)); @@ -167,6 +223,21 @@ public: BEAST_EXPECT(sle2->getAccountID(sfSponsorAccount) == sponsor2.id()); // dissolve sponsor + env(pay(alice, + sponsor2, + (env.balance(alice).value() - + env.current()->fees().reserve - + env.current()->fees().increment - XRP(1) + drops(1))), + fee(XRP(1))); + env.close(); + + env(sponsor::transfer(alice, checkId), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(pay(sponsor2, alice, XRP(1))); + env.close(); + env(sponsor::transfer(alice, checkId)); env.close(); diff --git a/src/xrpld/app/tx/detail/SponsorTransfer.cpp b/src/xrpld/app/tx/detail/SponsorTransfer.cpp index 72c05eaef7..d58d0b511f 100644 --- a/src/xrpld/app/tx/detail/SponsorTransfer.cpp +++ b/src/xrpld/app/tx/detail/SponsorTransfer.cpp @@ -111,8 +111,15 @@ TER SponsorTransfer::preclaim(PreclaimContext const& ctx) { auto const index = ctx.tx[~sfLedgerIndex]; + auto const newSponsor = getTxReserveSponsor(ctx.view, ctx.tx); - if (index) + bool const isObjectSponsor = index != std::nullopt; + + auto const accSle = ctx.view.read(keylet::account(ctx.tx[sfAccount])); + if (!accSle) + return tecINTERNAL; + + if (isObjectSponsor) { auto const sle = ctx.view.read(keylet::unchecked(*index)); if (!sle) @@ -123,33 +130,64 @@ SponsorTransfer::preclaim(PreclaimContext const& ctx) if (!owner) return tecNO_PERMISSION; - if (sle->isFieldPresent(sfSponsorAccount)) + if (newSponsor) { - auto const sponsor = sle->getAccountID(sfSponsorAccount); - if (sponsor == owner) - return tecNO_PERMISSION; - - // TODO: check reserve + if (sle->isFieldPresent(sfSponsorAccount)) + { + // transfer sponsor + if ((*newSponsor)->getAccountID(sfAccount) == owner) + return tecNO_PERMISSION; + } } else { - // TODO: check reserve + // dissolve sponsor + // check object is sponsored + if (!sle->isFieldPresent(sfSponsorAccount)) + return tecNO_PERMISSION; } + + // check account have sufficient balance + if (auto const ter = checkInsufficientReserve( + ctx.view, + accSle, + accSle->getFieldAmount(sfBalance), + newSponsor, + // TODO: address variable ownerCount like PriceOracle + 1); + !isTesSuccess(ter)) + return ter; } else { - auto const accSle = ctx.view.read(keylet::account(ctx.tx[sfAccount])); - if (!accSle) - return tecINTERNAL; - - if (accSle->isFieldPresent(sfSponsorAccount)) + if (newSponsor) { - // TODO: check reserve + if (accSle->isFieldPresent(sfSponsorAccount)) + { + // check not same account + if ((*newSponsor)->getAccountID(sfAccount) == + accSle->getAccountID(sfAccount)) + return tecNO_PERMISSION; + } } else { - // TODO: check reserve + // dissolve sponsor + // check account is sponsored + if (!accSle->isFieldPresent(sfSponsorAccount)) + return tecNO_PERMISSION; } + + // check account have sufficient balance + if (auto const ter = checkInsufficientReserve( + ctx.view, + accSle, + accSle->getFieldAmount(sfBalance), + newSponsor, + 0, + 1); + !isTesSuccess(ter)) + return ter; } return tesSUCCESS; @@ -161,12 +199,13 @@ SponsorTransfer::doApply() auto const& tx = ctx_.tx; auto const index = tx[~sfLedgerIndex]; + bool const isObjectSponsor = index != std::nullopt; auto const accSle = view().peek(keylet::account(account_)); if (!accSle) return tefINTERNAL; // LCOV_EXCL_LINE - if (index) + if (isObjectSponsor) { // transfer object sponsor auto const objSle = view().peek(keylet::unchecked(*index)); diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index 2c797a7120..2ae31a55d3 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -458,7 +458,8 @@ checkInsufficientReserve( std::shared_ptr accSle, STAmount const& accBalance, std::optional> const& _sponsorSle, - std::int32_t ownerCountDelta); + std::int32_t ownerCountDelta, + std::int32_t accountCountDelta = 0); std::optional> getTxReserveSponsor(ApplyView& view, STTx const& tx); diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index a6c0423e92..fe08e3a16c 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -1027,7 +1027,8 @@ checkInsufficientReserve( std::shared_ptr accSle, STAmount const& accBalance, std::optional> const& _sponsorSle, - std::int32_t ownerCountDelta) + std::int32_t ownerCountDelta, + std::int32_t accountCountDelta) { if (_sponsorSle.has_value()) { @@ -1037,8 +1038,9 @@ checkInsufficientReserve( sponsorSle->getFieldU32(sfOwnerCount), sponsorSle->getFieldU32(sfSponsoredOwnerCount), sponsorSle->getFieldU32(sfSponsoringOwnerCount) + ownerCountDelta, - sponsorSle->isFieldPresent(sfSponsor), - sponsorSle->getFieldU32(sfSponsoringAccountCount))}; + sponsorSle->isFieldPresent(sfSponsorAccount), + sponsorSle->getFieldU32(sfSponsoringAccountCount) + + accountCountDelta)}; if (sponsorBalance < sponsorReserve) return tecINSUFFICIENT_RESERVE; @@ -1049,8 +1051,8 @@ checkInsufficientReserve( accSle->getFieldU32(sfOwnerCount) + ownerCountDelta, accSle->getFieldU32(sfSponsoredOwnerCount), accSle->getFieldU32(sfSponsoringOwnerCount), - accSle->isFieldPresent(sfSponsor), - accSle->getFieldU32(sfSponsoringAccountCount))}; + accSle->isFieldPresent(sfSponsorAccount), + accSle->getFieldU32(sfSponsoringAccountCount) + accountCountDelta)}; if (accBalance < reserve) return tecINSUFFICIENT_RESERVE;