From 178bb8f7e9bcae79282d9c7d9645f6ce0a888cff Mon Sep 17 00:00:00 2001 From: tequ Date: Sun, 22 Feb 2026 00:01:56 +0900 Subject: [PATCH] Allow zero value for ReserveCount, FeeAmount, MaxFee --- .../tx/transactors/Sponsor/SponsorshipSet.cpp | 32 +++++++++++---- src/test/app/Sponsor_test.cpp | 41 ++++++++++++++++--- src/test/jtx/impl/sponsor.cpp | 11 +++++ src/test/jtx/sponsor.h | 3 ++ 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp b/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp index 114f6e530e..cefdbf8090 100644 --- a/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp +++ b/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp @@ -67,7 +67,7 @@ SponsorshipSet::preflight(PreflightContext const& ctx) if (!isXRP(amount)) return temBAD_AMOUNT; - if (amount.xrp() <= XRPAmount{0}) + if (amount.xrp() < XRPAmount{0}) return temBAD_AMOUNT; return tesSUCCESS; @@ -83,8 +83,8 @@ SponsorshipSet::preflight(PreflightContext const& ctx) if (ctx.tx.isFieldPresent(sfReserveCount)) { auto const reserveCount = ctx.tx.getFieldU32(sfReserveCount); - if (reserveCount < 1) - return temMALFORMED; + if (reserveCount < 0) + return temMALFORMED; // LCOV_EXCL_LINE } } @@ -227,14 +227,14 @@ SponsorshipSet::doApply() (*newSle)[sfOwner] = sponsorAcc; (*newSle)[sfSponsee] = sponseeAcc; - if (feeAmount) + if (feeAmount && *feeAmount > XRPAmount(0)) { (*sponsorAccSle)[sfBalance] -= *feeAmount; (*newSle)[sfFeeAmount] = *feeAmount; } - if (maxFee) + if (maxFee && *maxFee > XRPAmount(0)) (*newSle)[sfMaxFee] = *maxFee; - if (reserveCount) + if (reserveCount && *reserveCount > 0) (*newSle)[sfReserveCount] = *reserveCount; auto flags = 0; @@ -273,15 +273,29 @@ SponsorshipSet::doApply() if (feeAmountDelta != beast::zero) { (*sponsorAccSle)[sfBalance] -= feeAmountDelta; - (*sponsorObjSle).setFieldAmount(sfFeeAmount, *feeAmount); + + if (*feeAmount == XRPAmount(0)) + (*sponsorObjSle).makeFieldAbsent(sfFeeAmount); + else + (*sponsorObjSle).setFieldAmount(sfFeeAmount, *feeAmount); } } if (maxFee) - (*sponsorObjSle)[sfMaxFee] = *maxFee; + { + if (*maxFee == XRPAmount(0)) + (*sponsorObjSle).makeFieldAbsent(sfMaxFee); + else + (*sponsorObjSle)[sfMaxFee] = *maxFee; + } if (reserveCount) - (*sponsorObjSle)[sfReserveCount] = *reserveCount; + { + if (*reserveCount == 0) + (*sponsorObjSle).makeFieldAbsent(sfReserveCount); + else + (*sponsorObjSle)[sfReserveCount] = *reserveCount; + } // update Flags auto flags = sponsorObjSle->getFieldU32(sfFlags); diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 7d0afbeec9..f90eda8cee 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -128,23 +128,20 @@ public: ter(temMALFORMED)); // Invalid feeAmount - for (auto amt : {XRP(-1), XRP(0), USD(1)}) + for (auto amt : {XRP(-1), USD(1)}) { env(sponsor::set_fee(sponsor, 0, amt), sponsor::sponseeAcc(alice), ter(temBAD_AMOUNT)); } // Invalid MaxFee - for (auto amt : {XRP(-1), XRP(0), USD(1)}) + for (auto amt : {XRP(-1), USD(1)}) { env(sponsor::set_fee(sponsor, 0, XRP(1), amt), sponsor::sponseeAcc(alice), ter(temBAD_AMOUNT)); } - // Invalid reserveCount - env(sponsor::set_reserve(sponsor, 0, 0), sponsor::sponseeAcc(alice), ter(temMALFORMED)); - // Invalid Delete operation env(sponsor::set_reserve(sponsor, tfDeleteObject, 1), sponsor::sponseeAcc(alice), ter(temMALFORMED)); env(sponsor::set_fee(sponsor, tfDeleteObject, XRP(1)), sponsor::sponseeAcc(alice), ter(temMALFORMED)); - // TODO: test MaxFee with tfDeleteObject + env(sponsor::set_max_fee(sponsor, tfDeleteObject, XRP(1)), sponsor::sponseeAcc(alice), ter(temMALFORMED)); // Invalid SponsorAccount with non-Delete operation env(sponsor::set_reserve(sponsor, 0, 100), sponsor::counterpartySponsor(alice), ter(temMALFORMED)); @@ -334,6 +331,7 @@ public: env.close(); { + // create sponsorship env(sponsor::set( sponsor, tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve, @@ -401,6 +399,37 @@ public: // delete from sponsee env(sponsor::del(alice), sponsor::counterpartySponsor(sponsor), ter(tesSUCCESS)); env.close(); + BEAST_EXPECT(!env.le(keylet::sponsor(sponsor, alice))); + + // create sponsorship with zero value + env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)), sponsor::sponseeAcc(alice), fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsor(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFieldPresent(sfReserveCount)); + BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount)); + BEAST_EXPECT(!sle->isFieldPresent(sfMaxFee)); + + // update sponsorship with non-zero value + env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)), sponsor::sponseeAcc(alice), fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsor(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->at(sfReserveCount) == 100); + BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100)); + BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1)); + + // update sponsorship with zero value + env(sponsor::set(sponsor, 0, 0, XRP(0), XRP(0)), sponsor::sponseeAcc(alice), fee(XRP(1))); + env.close(); + + sle = env.le(keylet::sponsor(sponsor, alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(!sle->isFieldPresent(sfReserveCount)); + BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount)); + BEAST_EXPECT(!sle->isFieldPresent(sfMaxFee)); } { diff --git a/src/test/jtx/impl/sponsor.cpp b/src/test/jtx/impl/sponsor.cpp index d9b73dabed..b152d29676 100644 --- a/src/test/jtx/impl/sponsor.cpp +++ b/src/test/jtx/impl/sponsor.cpp @@ -56,6 +56,17 @@ set_reserve(jtx::Account const& account, uint32_t flags, uint32_t reserveCount) return jv; } +Json::Value +set_max_fee(jtx::Account const& account, uint32_t flags, STAmount maxFee) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::SponsorshipSet; + jv[jss::Account] = account.human(); + jv[sfFlags.jsonName] = flags; + jv[sfMaxFee.jsonName] = maxFee.getJson(JsonOptions::none); + return jv; +} + Json::Value del(jtx::Account const& account) { diff --git a/src/test/jtx/sponsor.h b/src/test/jtx/sponsor.h index b118bd4356..a03a64865b 100644 --- a/src/test/jtx/sponsor.h +++ b/src/test/jtx/sponsor.h @@ -28,6 +28,9 @@ set_fee( Json::Value set_reserve(jtx::Account const& account, std::uint32_t flags, std::uint32_t reserveCount); +Json::Value +set_max_fee(jtx::Account const& account, std::uint32_t flags, STAmount maxFee); + Json::Value del(jtx::Account const& account);