diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index f300c253ca..06a75e902d 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -463,34 +463,10 @@ getAsfFlagMap() #pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP") #pragma pop_macro("ACCOUNTSET_FLAGS") -#pragma push_macro("SPONSOR_FLAGS") -#pragma push_macro("SPONSOR_FLAG_TO_VALUE") -#pragma push_macro("SPONSOR_FLAG_TO_MAP") +// Sponsor flags (spf) -// Sponsor Flag values -#define SPONSOR_FLAGS(SPF_FLAG) \ - SPF_FLAG(spfSponsorFee, 1) \ - SPF_FLAG(spfSponsorReserve, 2) - -#define SPONSOR_FLAG_TO_VALUE(name, value) inline constexpr FlagValue name = value; -#define SPONSOR_FLAG_TO_MAP(name, value) {#name, value}, - -SPONSOR_FLAGS(SPONSOR_FLAG_TO_VALUE) - -inline std::map const& -getspfFlagMap() -{ - static std::map const flags = {SPONSOR_FLAGS(SPONSOR_FLAG_TO_MAP)}; - return flags; -} - -#undef SPONSOR_FLAG_TO_VALUE -#undef SPONSOR_FLAG_TO_MAP -#undef SPONSOR_FLAGS - -#pragma pop_macro("SPONSOR_FLAG_TO_VALUE") -#pragma pop_macro("SPONSOR_FLAG_TO_MAP") -#pragma pop_macro("SPONSOR_FLAGS") +inline constexpr FlagValue spfSponsorFee = 1; +inline constexpr FlagValue spfSponsorReserve = 2; } // namespace xrpl diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 40029f48a6..2b005e034a 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -613,7 +613,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ })) /** A ledger object representing a sponsorship. - \sa keylet::sponsor + \sa keylet::sponsorship */ LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({ {sfPreviousTxnID, SoeRequired}, diff --git a/include/xrpl/tx/invariants/SponsorshipInvariant.h b/include/xrpl/tx/invariants/SponsorshipInvariant.h index 8bae9d8d47..79dedfcd1d 100644 --- a/include/xrpl/tx/invariants/SponsorshipInvariant.h +++ b/include/xrpl/tx/invariants/SponsorshipInvariant.h @@ -22,7 +22,7 @@ class SponsorshipOwnerCountsMatch std::int64_t deltaSponsoredOwnerCount_ = 0; std::int64_t deltaSponsoringOwnerCount_ = 0; std::int64_t deltaSponsoredObjectOwnerCount_ = 0; - std::uint64_t invalidOwnerCountLessThanSponsoredOwnerCount_ = 0; + std::uint64_t ownerCountBelowSponsored_ = 0; public: void diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index c77f49c864..79cf6eb0e4 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -753,7 +753,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner) { Throw( "NFTokenPage directory for " + to_string(owner) + - " cannot be repaired. std::unexpected link problem."); + " cannot be repaired. Unexpected link problem."); } newPrev->at(sfNextPageMin) = nextPage->key(); view.update(newPrev); diff --git a/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp index 41d55e9019..f4db96a643 100644 --- a/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp +++ b/src/libxrpl/tx/invariants/SponsorshipInvariant.cpp @@ -93,7 +93,7 @@ SponsorshipOwnerCountsMatch::visitEntry( (afterSponsoredObjectOwnerCount - beforeSponsoredObjectOwnerCount); if (getOwnerCount(after) < getSponsored(after)) - invalidOwnerCountLessThanSponsoredOwnerCount_ += 1; + ownerCountBelowSponsored_ += 1; } bool @@ -111,6 +111,13 @@ SponsorshipOwnerCountsMatch::finalize( return false; } + if (ownerCountBelowSponsored_ > 0) + { + JLOG(j.fatal()) + << "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount."; + return false; + } + if (deltaSponsoredObjectOwnerCount_ != deltaSponsoredOwnerCount_) { JLOG(j.fatal()) << "Invariant failed: SponsoredObjectOwnerCount does not " @@ -118,13 +125,6 @@ SponsorshipOwnerCountsMatch::finalize( return false; } - if (invalidOwnerCountLessThanSponsoredOwnerCount_ > 0) - { - JLOG(j.fatal()) - << "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount."; - return false; - } - return true; } diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index eef6af1fea..c02075ad37 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -5077,6 +5077,7 @@ public: testInvariantOverwrite(defaultAmendments() - fixCleanup3_1_3); testVaultComputeCoarsestScale(); testAMM(); + testSponsorship(); } }; diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index dd9eb1c119..69f1e28170 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -93,6 +94,8 @@ std::vector> gMappings{ {jss::oracle_document_id, FieldType::UInt32Field}, {jss::owner, FieldType::AccountField}, {jss::seq, FieldType::UInt32Field}, + {jss::sponsor, FieldType::AccountField}, + {jss::sponsee, FieldType::AccountField}, {jss::subject, FieldType::AccountField}, {jss::ticket_seq, FieldType::UInt32Field}, }; @@ -107,7 +110,7 @@ getFieldType(json::StaticString fieldName) return it->second; } - Throw("`mappings` is missing field " + std::string(fieldName.cStr())); + Throw("`gMappings` is missing field " + std::string(fieldName.cStr())); } std::string @@ -1886,6 +1889,59 @@ class LedgerEntry_test : public beast::unit_test::Suite runLedgerEntryTest(env, jss::signer_list); } + void + testSponsorship() + { + testcase("Sponsorship"); + + using namespace test::jtx; + + Env env{*this}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + env(sponsor::set(alice, 0), sponsor::SponseeAcc(bob)); + env.close(); + std::string const ledgerHash{to_string(env.closed()->header().hash)}; + auto const sponsorshipIndex = to_string(keylet::sponsorship(alice.id(), bob.id()).key); + + { + // Request by sponsor and sponsee. + json::Value jvParams; + jvParams[jss::sponsorship][jss::sponsor] = alice.human(); + jvParams[jss::sponsorship][jss::sponsee] = bob.human(); + jvParams[jss::ledger_hash] = ledgerHash; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human()); + BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString()); + } + { + // Request by index. + json::Value jvParams; + jvParams[jss::sponsorship] = sponsorshipIndex; + jvParams[jss::ledger_hash] = ledgerHash; + json::Value const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Sponsorship); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfSponsee.jsonName] == bob.human()); + BEAST_EXPECT(sponsorshipIndex == jrr[jss::node][jss::index].asString()); + } + { + // Check all malformed cases. + runLedgerEntryTest( + env, + jss::sponsorship, + { + {.fieldName = jss::sponsor, .malformedErrorMsg = "malformedSponsor"}, + {.fieldName = jss::sponsee, .malformedErrorMsg = "malformedSponsee"}, + }); + } + } + void testTicket() { @@ -2678,6 +2734,7 @@ public: testPayChan(); testRippleState(); testSignerList(); + testSponsorship(); testTicket(); testDID(); testInvalidOracleLedgerEntry(); diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp index 91a272798e..b4f1a55689 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp @@ -719,6 +719,30 @@ parseSignerList( return parseObjectID(params, fieldName, "hex string"); } +static std::expected +parseSponsorship( + json::Value const& params, + json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) +{ + if (!params.isObject()) + { + return parseObjectID(params, fieldName); + } + + auto const sponsorID = + LedgerEntryHelpers::requiredAccountID(params, jss::sponsor, "malformedSponsor"); + if (!sponsorID) + return std::unexpected(sponsorID.error()); + + auto const sponseeID = + LedgerEntryHelpers::requiredAccountID(params, jss::sponsee, "malformedSponsee"); + if (!sponseeID) + return std::unexpected(sponseeID.error()); + + return keylet::sponsorship(*sponsorID, *sponseeID).key; +} + static std::expected parseTicket( json::Value const& params, @@ -764,30 +788,6 @@ parseVault( return keylet::vault(*id, *seq).key; } -static std::expected -parseSponsorship( - json::Value const& params, - json::StaticString const fieldName, - [[maybe_unused]] unsigned const apiVersion) -{ - if (!params.isObject()) - { - return parseObjectID(params, fieldName); - } - - auto const sponsorAccountID = - LedgerEntryHelpers::requiredAccountID(params, jss::sponsor, "malformedSponsor"); - if (!sponsorAccountID) - return std::unexpected(sponsorAccountID.error()); - - auto const sponseeAccountID = - LedgerEntryHelpers::requiredAccountID(params, jss::sponsee, "malformedSponsee"); - if (!sponseeAccountID) - return std::unexpected(sponseeAccountID.error()); - - return keylet::sponsorship(*sponsorAccountID, *sponseeAccountID).key; -} - static std::expected parseXChainOwnedClaimID( json::Value const& claimId,