From d5a1314c4794e1d7735d472ed6e6ce9365ecde01 Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 24 Sep 2025 19:05:22 +0900 Subject: [PATCH] test Sponsor Reserve checks for TrustSet --- include/xrpl/ledger/View.h | 3 + src/libxrpl/ledger/View.cpp | 14 ++- src/test/app/Sponsor_test.cpp | 155 +++++++++++++++++---------- src/xrpld/app/tx/detail/SetTrust.cpp | 13 ++- 4 files changed, 123 insertions(+), 62 deletions(-) diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 433418cd70..ca8867f0f5 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -451,6 +451,9 @@ areCompatible( beast::Journal::Stream& s, char const* reason); +uint32_t +ownerCount(std::shared_ptr const& sponsorSle); + TER checkInsufficientReserve( ReadView const& view, diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 56a31019c4..d2fd26809b 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1029,6 +1029,18 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal) return std::nullopt; } +uint32_t +ownerCount(std::shared_ptr const& sponsorSle) +{ + auto const ownerCount = sponsorSle->getFieldU32(sfOwnerCount); + auto const sponsoredOwnerCount = + sponsorSle->getFieldU32(sfSponsoredOwnerCount); + auto const sponsoringOwnerCount = + sponsorSle->getFieldU32(sfSponsoringOwnerCount); + + return ownerCount + sponsoringOwnerCount - sponsoredOwnerCount; +} + TER checkInsufficientReserve( ReadView const& view, @@ -1479,7 +1491,7 @@ authorizeMPToken( // an account owns, in the case of MPTokens we only // *enforce* a reserve if the user owns more than two // items. This is similar to the reserve requirements of trust lines. - if (sleAcct->getFieldU32(sfOwnerCount) >= 2) + if (ownerCount(sponsor.value_or(sleAcct)) >= 2) { if (auto const ret = checkInsufficientReserve( view, sleAcct, priorBalance, sponsor, 1); diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index cfde84c549..3ba39ada4b 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -47,10 +47,10 @@ adjustAccountXRPBalance( return; if (currentBalance > balanceTo) - env(pay(account, env.master, currentBalance - balanceTo + XRP(1)), + env(pay(account, env.master, currentBalance - (balanceTo + XRP(1))), fee(XRP(1))); else - env(pay(env.master, account, balanceTo - currentBalance), fee(XRP(1))); + env(pay(env.master, account, balanceTo - currentBalance)); env.close(); } @@ -1968,10 +1968,13 @@ public: env.close(); // for free mptoken checks - std::uint32_t bobTicketSeq{env.seq(bob) + 1}; - env(ticket::create(bob, 2)); + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + std::uint32_t ticketSeq{env.seq(sponsor) + 1}; + env(ticket::create(sponsor, 2)); env.close(); + adjustAccountXRPBalance( + env, sponsor, reserve(env, 3) - drops(1)); jv = {}; jv[sfTransactionType] = jss::MPTokenAuthorize; jv[sfAccount] = bob.human(); @@ -1983,9 +1986,12 @@ public: ter(tecINSUFFICIENT_RESERVE)); env.close(); - env(noop(bob), ticket::use(bobTicketSeq)); + env(noop(sponsor), ticket::use(ticketSeq)); env.close(); + adjustAccountXRPBalance( + env, sponsor, reserve(env, 2) - drops(1)); + // pass (free mptoken) env(jv, sponsor::as(sponsor, tfSponsorReserve), @@ -2822,71 +2828,112 @@ public: { testcase("TrustSet"); using namespace test::jtx; - Env env{*this, testable_amendments()}; Account const alice("alice"); Account const bob("bob"); Account const sponsor("sponsor"); Account const sponsor2("sponsor2"); - env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); - env.close(); - - auto const& highAcc = alice > bob ? alice : bob; - auto const& lowAcc = alice > bob ? bob : alice; - for (bool isIssuerHigh : {false, true}) { - auto const& issuer = isIssuerHigh ? highAcc : lowAcc; - auto const& user = isIssuerHigh ? lowAcc : highAcc; - - auto const USD = issuer["USD"]; - - // create TrustLine - env(trust(user, USD(100)), - sponsor::as(sponsor, tfSponsorReserve), - sponsor::sig(sponsor)); + Env env{*this, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); env.close(); - BEAST_EXPECT(ownerCount(env, user) == 1); - BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + auto const& highAcc = alice > bob ? alice : bob; + auto const& lowAcc = alice > bob ? bob : alice; + for (bool isIssuerHigh : {false, true}) + { + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; - auto const line = env.le(keylet::line(user, issuer, USD.currency)); - BEAST_EXPECT( - line->getAccountID( - isIssuerHigh ? sfLowSponsorAccount - : sfHighSponsorAccount) == sponsor.id()); - BEAST_EXPECT(!line->isFieldPresent( - isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount)); + auto const USD = issuer["USD"]; - // transfer sponsor - env(sponsor::transfer( - user, keylet::line(user, issuer, USD.currency).key), - sponsor::as(sponsor2, tfSponsorReserve), - sponsor::sig(sponsor2)); + // create TrustLine + env(trust(user, USD(100)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, user) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + auto const line = + env.le(keylet::line(user, issuer, USD.currency)); + BEAST_EXPECT( + line->getAccountID( + isIssuerHigh ? sfLowSponsorAccount + : sfHighSponsorAccount) == sponsor.id()); + BEAST_EXPECT(!line->isFieldPresent( + isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount)); + + // transfer sponsor + env(sponsor::transfer( + user, keylet::line(user, issuer, USD.currency).key), + sponsor::as(sponsor2, tfSponsorReserve), + sponsor::sig(sponsor2)); + env.close(); + + BEAST_EXPECT(ownerCount(env, user) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + auto const line2 = + env.le(keylet::line(user, issuer, USD.currency)); + BEAST_EXPECT( + line2->getAccountID( + isIssuerHigh ? sfLowSponsorAccount + : sfHighSponsorAccount) == sponsor2.id()); + BEAST_EXPECT(!line2->isFieldPresent( + isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount)); + + // delete TrustLine + env(trust(user, USD(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, user) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + BEAST_EXPECT(!env.le(keylet::line(user, issuer, USD.currency))); + } + } + + { + // check INSUFFICIENT_RESERVE for TrustSet + Env env{*this, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, sponsor); env.close(); - BEAST_EXPECT(ownerCount(env, user) == 1); - BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + auto const& highAcc = alice > bob ? alice : bob; + auto const& lowAcc = alice > bob ? bob : alice; + for (bool isIssuerHigh : {false}) + { + auto const& issuer = isIssuerHigh ? highAcc : lowAcc; + auto const& user = isIssuerHigh ? lowAcc : highAcc; + auto const USD = issuer["USD"]; - auto const line2 = env.le(keylet::line(user, issuer, USD.currency)); - BEAST_EXPECT( - line2->getAccountID( - isIssuerHigh ? sfLowSponsorAccount - : sfHighSponsorAccount) == sponsor2.id()); - BEAST_EXPECT(!line2->isFieldPresent( - isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount)); + adjustAccountXRPBalance(env, sponsor, reserve(env, 100)); - // delete TrustLine - env(trust(user, USD(0))); - env.close(); + // free trustline + std::uint32_t const ticketSeq{env.seq(sponsor) + 1}; + env(ticket::create(sponsor, 2)); + env.close(); - BEAST_EXPECT(ownerCount(env, user) == 0); - BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + adjustAccountXRPBalance( + env, sponsor, reserve(env, 3) - drops(1)); - BEAST_EXPECT(!env.le(keylet::line(user, issuer, USD.currency))); + // create TrustLine + env(trust(user, USD(100)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor), + ter(tecNO_LINE_INSUF_RESERVE)); + env.close(); + + env(noop(sponsor), ticket::use(ticketSeq)); + env(noop(sponsor), ticket::use(ticketSeq + 1)); + env.close(); + } } } diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 6725ecc958..7e1e2a77fa 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -382,8 +382,6 @@ SetTrust::doApply() if (!sle) return tefINTERNAL; - std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount); - // The reserve that is required to create the line. Note // that although the reserve increases with every item // an account owns, in the case of trust lines we only @@ -401,7 +399,13 @@ SetTrust::doApply() // but the incremental reserve for the trust line as // well. A person with no intention of using the gateway // could use the extra XRP for their own purposes. + auto const txSponsorAcc = getTxReserveSponsorAccountID(ctx_.tx); + std::optional> txSponsorSle = std::nullopt; + if (txSponsorAcc) + txSponsorSle = view().peek(keylet::account(*txSponsorAcc)); + + std::uint32_t const uOwnerCount = ownerCount(txSponsorSle.value_or(sle)); bool const freeTrustLine = uOwnerCount < 2; std::uint32_t uQualityIn(bQualityIn ? ctx_.tx.getFieldU32(sfQualityIn) : 0); @@ -453,11 +457,6 @@ SetTrust::doApply() SLE::pointer sleRippleState = view().peek(keylet::line(account_, uDstAccountID, currency)); - auto const txSponsorAcc = getTxReserveSponsorAccountID(ctx_.tx); - std::optional> txSponsorSle = std::nullopt; - if (txSponsorAcc) - txSponsorSle = view().peek(keylet::account(*txSponsorAcc)); - if (sleRippleState) { STAmount saLowBalance;