From 453fd23512b5673b64abf9966ea5333065468813 Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 19 Sep 2025 21:06:34 +0900 Subject: [PATCH] add Sponsor Oracle --- src/test/app/Sponsor_test.cpp | 252 +++++++++++++++++- src/test/jtx.h | 1 + src/xrpld/app/tx/detail/SetOracle.cpp | 43 ++- .../app/tx/detail/SponsorshipTransfer.cpp | 30 ++- 4 files changed, 314 insertions(+), 12 deletions(-) diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index e4a20ff0c9..b852d91b07 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -19,6 +19,7 @@ #include +#include #include namespace ripple { @@ -1861,6 +1862,255 @@ public: void testOracle() { + testcase("Oracle"); + using namespace test::jtx; + using namespace std::chrono; + using DataSeries = std::vector< + std::tuple>; + + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + Account const sponsor2("sponsor2"); + + env.fund(XRP(1000000), alice, sponsor, sponsor2); + env.close(); + + auto const oracleSet = + [&env](Account const& account, uint8_t dataSeriesSize) { + auto const now = env.timeKeeper().now(); + env.close(now + oracle::testStartTime - epoch_offset); + Json::Value jv; + jv[jss::TransactionType] = jss::OracleSet; + jv[jss::Account] = to_string(account); + jv[jss::OracleDocumentID] = 1; + jv[jss::LastUpdateTime] = to_string( + duration_cast( + env.current()->info().closeTime.time_since_epoch()) + .count() + + epoch_offset.count() + 100); + jv[jss::PriceDataSeries] = Json::arrayValue; + jv[jss::Provider] = strHex(std::string{"provider"}); + jv[jss::AssetClass] = strHex(std::string{"currency"}); + + DataSeries const series = { + {"XRP", "US1", 740, 1}, + {"XRP", "US2", 750, 1}, + {"XRP", "US3", 740, 1}, + {"XRP", "US4", 750, 1}, + {"XRP", "US5", 740, 1}, + {"XRP", "US6", 750, 1}, + {"XRP", "US7", 740, 1}, + {"XRP", "US8", 750, 1}, + {"XRP", "US9", 740, 1}, + {"XRP", "U10", 750, 1}, + }; + + DataSeries actualSeries( + series.begin(), series.begin() + dataSeriesSize); + + Json::Value dataSeries(Json::arrayValue); + for (auto const& data : actualSeries) + { + Json::Value priceData; + Json::Value price; + price[jss::BaseAsset] = std::get<0>(data); + price[jss::QuoteAsset] = std::get<1>(data); + price[jss::AssetPrice] = std::get<2>(data); + price[jss::Scale] = std::get<3>(data); + priceData[jss::PriceData] = price; + dataSeries.append(priceData); + } + jv[jss::PriceDataSeries] = dataSeries; + return jv; + }; + + auto const oracleDelete = [&](Account const& account) { + Json::Value jv; + jv[jss::TransactionType] = jss::OracleDelete; + jv[jss::Account] = to_string(account); + jv[jss::OracleDocumentID] = 1; + return jv; + }; + + { + // OracleSet (reserve 1) + env(oracleSet(alice, 5), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // transfer sponsor + env(sponsor::transfer(alice, keylet::oracle(alice, 1).key), + sponsor::as(sponsor2, tfSponsorReserve), + sponsor::sig(sponsor2)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); + + // OracleDelete + env(oracleDelete(alice)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + { + // OracleSet (reserve 2) + env(oracleSet(alice, 6), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + + // transfer sponsor + env(sponsor::transfer(alice, keylet::oracle(alice, 1).key), + sponsor::as(sponsor2, tfSponsorReserve), + sponsor::sig(sponsor2)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2); + + // OracleDelete + env(oracleDelete(alice)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + { + // OracleSet (reserve 1->2, sponsor1 -> no-sponsor) + env(oracleSet(alice, 5), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // reserve 1->2 + env(oracleSet(alice, 6)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + + // OracleDelete + env(oracleDelete(alice)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + { + // OracleSet (reserve 1->2, sponsor1 -> sponsor2) + env(oracleSet(alice, 5), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + // reserve 1->2 + env(oracleSet(alice, 6), + sponsor::as(sponsor2, tfSponsorReserve), + sponsor::sig(sponsor2)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2); + + // OracleDelete + env(oracleDelete(alice)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } + { + // OracleSet (reserve 1->2, non-sponsor -> sponsor1) + env(oracleSet(alice, 5)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); + + // reserve 1->2 + env(oracleSet(alice, 6), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + + // OracleDelete + env(oracleDelete(alice)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 0); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + for (bool isTwoOwnerCount : {false, true}) + { + // test sponsor transfer + auto const dataSeriesSize = isTwoOwnerCount ? 6 : 5; + auto const ocount = isTwoOwnerCount ? 2 : 1; + env(oracleSet(alice, dataSeriesSize), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == ocount); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == ocount); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == ocount); + + // transfer sponsor + env(sponsor::transfer(alice, keylet::oracle(alice, 1).key), + sponsor::as(sponsor2, tfSponsorReserve), + sponsor::sig(sponsor2)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == ocount); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == ocount); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == ocount); + + // disolve sponsor + env(sponsor::transfer(alice, keylet::oracle(alice, 1).key)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == ocount); + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); + } } void @@ -2189,7 +2439,7 @@ public: testNFTokenOffer(); testPayChan(); testPermissionedDomain(); - // testOracle(); + testOracle(); testSignerList(); testTrustSet(); // testVault(); diff --git a/src/test/jtx.h b/src/test/jtx.h index 79d92d03da..62cb5829c4 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp index d2e1cfa1ae..2c52d28766 100644 --- a/src/xrpld/app/tx/detail/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -183,13 +183,15 @@ SetOracle::preclaim(PreclaimContext const& ctx) } static bool -adjustOwnerCount(ApplyContext& ctx, int count) +adjustOwnerCount( + ApplyContext& ctx, + std::optional> const& sponsor, + int count) { if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount]))) { - adjustOwnerCount( - ctx.view(), sleAccount, std::nullopt, count, ctx.journal); + adjustOwnerCount(ctx.view(), sleAccount, sponsor, count, ctx.journal); return true; } @@ -275,8 +277,34 @@ SetOracle::doApply() auto const newCount = pairs.size() > 5 ? 2 : 1; auto const adjust = newCount - oldCount; - if (adjust != 0 && !adjustOwnerCount(ctx_, adjust)) - return tefINTERNAL; // LCOV_EXCL_LINE + + if (adjust > 0) + { + // To continue receiving sponsorship from the same account after the + // OwnerCount increases from 1 to 2, it is necessary to sign with + // the sponsor decrease current sponsored owner count. + // Otherwise, the sponsorship will be deleted. + + auto const currentsponsor = + getLedgerEntryReserveSponsor(ctx_.view(), sle); + auto const newSponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + + // decrease current sponsored owner count + if (!adjustOwnerCount(ctx_, currentsponsor, -oldCount)) + return tefINTERNAL; // LCOV_EXCL_LINE + removeSponsorFromLedgerEntry(sle); + // increase new owner count + if (!adjustOwnerCount(ctx_, newSponsor, newCount)) + return tefINTERNAL; // LCOV_EXCL_LINE + addSponsorToLedgerEntry(sle, newSponsor); + } + else if (adjust < 0) + { + // decrease owner count + auto const sponsor = getLedgerEntryReserveSponsor(ctx_.view(), sle); + if (!adjustOwnerCount(ctx_, sponsor, adjust)) + return tefINTERNAL; // LCOV_EXCL_LINE + } ctx_.view().update(sle); } @@ -321,9 +349,12 @@ SetOracle::doApply() (*sle)[sfOwnerNode] = *page; auto const count = series.size() > 5 ? 2 : 1; - if (!adjustOwnerCount(ctx_, count)) + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + if (!adjustOwnerCount(ctx_, sponsor, count)) return tefINTERNAL; // LCOV_EXCL_LINE + addSponsorToLedgerEntry(sle, sponsor); + ctx_.view().insert(sle); } diff --git a/src/xrpld/app/tx/detail/SponsorshipTransfer.cpp b/src/xrpld/app/tx/detail/SponsorshipTransfer.cpp index 330f145851..03c20668f0 100644 --- a/src/xrpld/app/tx/detail/SponsorshipTransfer.cpp +++ b/src/xrpld/app/tx/detail/SponsorshipTransfer.cpp @@ -112,6 +112,20 @@ getLedgerEntryOwner( }; } +template +inline std::uint32_t +getLedgerEntryOwnerCount(T const& sle) +{ + switch (sle->getType()) + { + case ltORACLE: { + return sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1; + } + default: + return 1; + } +}; + TER SponsorshipTransfer::preclaim(PreclaimContext const& ctx) { @@ -225,6 +239,8 @@ SponsorshipTransfer::doApply() if (!ownerSle) return tefINTERNAL; // LCOV_EXCL_LINE + auto const ownerCountDelta = getLedgerEntryOwnerCount(objSle); + if (tx.isFieldPresent(sfSponsor)) { auto const sponsorObj = tx.getFieldObject(sfSponsor); @@ -235,7 +251,8 @@ SponsorshipTransfer::doApply() view().peek(keylet::account(oldSponsor))) { auto const newCount = - oldSponsorSle->getFieldU32(sfSponsoringOwnerCount) - 1; + oldSponsorSle->getFieldU32(sfSponsoringOwnerCount) - + ownerCountDelta; if (newCount == 0) oldSponsorSle->makeFieldAbsent(sfSponsoringOwnerCount); else @@ -249,14 +266,16 @@ SponsorshipTransfer::doApply() // update owner's sponsored count ownerSle->setFieldU32( sfSponsoredOwnerCount, - ownerSle->getFieldU32(sfSponsoredOwnerCount) + 1); + ownerSle->getFieldU32(sfSponsoredOwnerCount) + + ownerCountDelta); view().update(ownerSle); } // increment new sponsoring count auto const newSponsorSle = view().peek(keylet::account(newSponsor)); newSponsorSle->setFieldU32( sfSponsoringOwnerCount, - newSponsorSle->getFieldU32(sfSponsoringOwnerCount) + 1); + newSponsorSle->getFieldU32(sfSponsoringOwnerCount) + + ownerCountDelta); view().update(newSponsorSle); objSle->setAccountID(sfSponsorAccount, newSponsor); @@ -268,7 +287,7 @@ SponsorshipTransfer::doApply() auto const oldSponsor = objSle->getAccountID(sfSponsorAccount); // decrement sponsored count auto const newCount = - accSle->getFieldU32(sfSponsoredOwnerCount) - 1; + accSle->getFieldU32(sfSponsoredOwnerCount) - ownerCountDelta; if (newCount == 0) accSle->makeFieldAbsent(sfSponsoredOwnerCount); else @@ -281,7 +300,8 @@ SponsorshipTransfer::doApply() { oldSponsorSle->setFieldU32( sfSponsoringOwnerCount, - oldSponsorSle->getFieldU32(sfSponsoringOwnerCount) - 1); + oldSponsorSle->getFieldU32(sfSponsoringOwnerCount) - + ownerCountDelta); view().update(oldSponsorSle); }