diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index e2c846d5ae..2aa49be96e 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -169,6 +169,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({ {sfVaultID, soeOPTIONAL}, // pseudo-account designator {sfSponsoredOwnerCount, soeOPTIONAL}, {sfSponsoringOwnerCount, soeOPTIONAL}, + {sfSponsoringAccountCount,soeOPTIONAL}, })) /** A ledger object which contains a list of object identifiers. diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 1d1637e2f8..2bfe555043 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -116,6 +116,7 @@ TYPED_SFIELD(sfOracleDocumentID, UINT32, 51) TYPED_SFIELD(sfPermissionValue, UINT32, 52) TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 53) TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 54) +TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 55) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index c89a90025f..4b82b1acfd 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -178,7 +178,8 @@ InnerObjectFormats::InnerObjectFormats() { {sfAccount, soeREQUIRED}, {sfFlags, soeREQUIRED}, - {sfSignature, soeOPTIONAL}, + {sfSigningPubKey, soeOPTIONAL}, + {sfTxnSignature, soeOPTIONAL}, {sfSigners, soeOPTIONAL}, }); } diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp new file mode 100644 index 0000000000..e7d815dfdd --- /dev/null +++ b/src/test/app/Sponsor_test.cpp @@ -0,0 +1,423 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +#include + +namespace ripple { + +class Sponsor_test : public beast::unit_test::suite +{ +public: + void + testSponsorFee() + { + using namespace test::jtx; + + testcase("Sponsor Fee"); + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + + env(noop(alice), + fee(XRP(1)), + sponsor::as(sponsor, tfSponsorFee), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == XRP(10000)); + BEAST_EXPECT(env.balance(sponsor) == XRP(9999)); + } + + void + testSponsorAccount() + { + testcase("Sponsor Account"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + + Account const alice("alice"); + Account const sponsor("sponsor"); + env.fund(XRP(10000), alice, sponsor); + + Account const bob("bob"); + env(pay(alice, bob, STAmount(env.current()->fees().accountReserve(0))), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(sponsored_owners(alice, 0)); + env.require(sponsoring_account_count(sponsor, 1)); + } + + void + testCheck() + { + testcase("Check"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + env.fund(XRP(10000), alice, bob, sponsor); + + // CheckCreate + auto const seq = env.seq(alice); + env(check::create(alice, bob, XRP(1)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 1)); + env.require(sponsoring_count(sponsor, 1)); + + // CheckCancel + auto const checkId = keylet::check(alice, seq).key; + env(check::cancel(alice, checkId)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 0)); + env.require(sponsoring_count(sponsor, 0)); + + auto const seq2 = env.seq(alice); + env(check::create(alice, bob, XRP(1)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + // CheckCash + auto const checkId2 = keylet::check(alice, seq2).key; + env(check::cash(bob, checkId2, XRP(1))); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 0)); + env.require(sponsoring_count(sponsor, 0)); + + // printf( + // "meta: %s\n", + // env.meta()->getJson(JsonOptions::none).toStyledString().c_str()); + } + + void + testOfffer() + { + testcase("Offer"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const gw("gw"); + Account const sponsor("sponsor"); + + auto USD = gw["USD"]; + + env.fund(XRP(10000), alice, gw, sponsor); + + // OfferCreate + auto const seq = env.seq(alice); + env(offer(alice, USD(1), XRP(1)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 1)); + env.require(sponsoring_count(sponsor, 1)); + + // OfferCancel + env(offer_cancel(alice, seq)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 0)); + env.require(sponsoring_count(sponsor, 0)); + + // TODO: test Execution + } + + void + testTicket() + { + testcase("Ticket"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const sponsor("master"); + + env.fund(XRP(1000000), alice, sponsor); + + // TicketCreate + std::uint32_t const ticketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 250), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 250)); + env.require(sponsoring_count(sponsor, 250)); + + // use a Ticket + env(noop(alice), ticket::use(ticketSeq + 1)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 249)); + env.require(sponsoring_count(sponsor, 249)); + } + + void + testCredentials() + { + testcase("Credentials"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + Account const issuer("issuer"); + Account const subject("subject"); + Account const sponsor("sponsor"); + + env.fund(XRP(1000000), issuer, subject, sponsor); + + // CredentialsCreate + env(credentials::create(subject, issuer, "credType"), + credentials::uri("uri"), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(issuer, 0)); + env.require(sponsored_owners(issuer, 1)); + env.require(sponsoring_count(sponsor, 1)); + + // CredentialsAccept + env(credentials::accept(subject, issuer, "credType"), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(issuer, 0)); + env.require(owners(subject, 0)); + env.require(sponsored_owners(issuer, 0)); + env.require(sponsored_owners(subject, 1)); + env.require(sponsoring_count(sponsor, 1)); + + // CredentialsDelete + env(credentials::deleteCred(subject, subject, issuer, "credType")); + env.close(); + + env.require(owners(issuer, 0)); + env.require(owners(subject, 0)); + env.require(sponsored_owners(issuer, 0)); + env.require(sponsored_owners(subject, 0)); + env.require(sponsoring_count(sponsor, 0)); + } + + void + testDelegate() + { + testcase("Delegate"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const bob("bob"); + Account const sponsor("sponsor"); + + env.fund(XRP(1000000), alice, bob, sponsor); + + // DelegateSet + env(delegate::set(alice, bob, {"Payment"}), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 1)); + env.require(sponsoring_count(sponsor, 1)); + + // delete + env(delegate::set(alice, bob, {})); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 0)); + env.require(sponsoring_count(sponsor, 0)); + } + + void + testDepositPreauth() + { + } + + void + testDID() + { + testcase("DID"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + + env.fund(XRP(1000000), alice, sponsor); + + // DIDSet + env(did::set(alice), + did::uri("uri"), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 1)); + env.require(sponsoring_count(sponsor, 1)); + + // DIDDelete + env(did::del(alice)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 0)); + env.require(sponsoring_count(sponsor, 0)); + } + + void + testEscrow() + { + } + + void + testMPToken() + { + } + + void + testNFToken() + { + } + + void + testNFTokenOffer() + { + } + + void + testPayChan() + { + } + + void + testPermissionedDomain() + { + } + + void + testOracle() + { + } + + void + testSignerList() + { + testcase("SignerList"); + using namespace test::jtx; + Env env{*this, testable_amendments()}; + Account const alice("alice"); + Account const sponsor("sponsor"); + + env.fund(XRP(1000000), alice, sponsor); + + Account const bob("bob"); + + // SignerListSet + env(signers(alice, 1, {{bob, 1}}), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 1)); + env.require(sponsoring_count(sponsor, 1)); + + // Delete + env(signers(alice, none)); + env.close(); + + env.require(owners(alice, 0)); + env.require(sponsored_owners(alice, 0)); + env.require(sponsoring_count(sponsor, 0)); + } + + void + testTrust() + { + } + + void + testVault() + { + } + + void + testXChain() + { + } + + void + testSponsorReserve() + { + testCheck(); + testOfffer(); + testTicket(); + testCredentials(); + testDelegate(); + // testDepositPreauth(); + testDID(); + // testEscrow(); + // testMPToken(); + // testNFToken(); + // testNFTokenOffer(); + // testPayChan(); + // testPermissionedDomain(); + // testOracle(); + testSignerList(); + // testTrust(); + // testVault(); + // testXChain(); + } + + void + run() override + { + testSponsorFee(); + testSponsorAccount(); + testSponsorReserve(); + } +}; + +BEAST_DEFINE_TESTSUITE(Sponsor, app, ripple); + +} // namespace ripple diff --git a/src/test/jtx/impl/owners.cpp b/src/test/jtx/impl/owners.cpp index 386ec29a37..00e5a4039d 100644 --- a/src/test/jtx/impl/owners.cpp +++ b/src/test/jtx/impl/owners.cpp @@ -55,6 +55,27 @@ owners::operator()(Env& env) const env.test.expect(env.le(account_)->getFieldU32(sfOwnerCount) == value_); } +void +sponsored_owners::operator()(Env& env) const +{ + env.test.expect( + env.le(account_)->getFieldU32(sfSponsoredOwnerCount) == value_); +} + +void +sponsoring_count::operator()(Env& env) const +{ + env.test.expect( + env.le(account_)->getFieldU32(sfSponsoringOwnerCount) == value_); +} + +void +sponsoring_account_count::operator()(Env& env) const +{ + env.test.expect( + env.le(account_)->getFieldU32(sfSponsoringAccountCount) == value_); +} + } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/impl/sponsor.cpp b/src/test/jtx/impl/sponsor.cpp new file mode 100644 index 0000000000..2db16a80db --- /dev/null +++ b/src/test/jtx/impl/sponsor.cpp @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace sponsor { + +Json::Value +transferAccount(jtx::Account const& account) +{ + Json::Value jv; + jv[jss::TransactionType] = "SponsorTransfer"; + jv[sfSponsor.jsonName] = account.human(); + + return jv; +} + +Json::Value +transferObject(uint256 const& id) +{ + Json::Value jv; + jv[jss::TransactionType] = "SponsorTransfer"; + jv[sfLedgerIndex.jsonName] = to_string(id); + return jv; +} + +void +as::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfSponsor.jsonName][sfAccount.jsonName] = sponsor_.human(); + jt.jv[sfSponsor.jsonName][sfFlags.jsonName] = flags; +} + +void +sig::operator()(Env& env, JTx& jt) const +{ + std::optional st; + try + { + // required to cast the STObject to STTx + jt.jv[jss::SigningPubKey] = ""; + st = parse(jt.jv); + } + catch (parse_error const&) + { + env.test.log << pretty(jt.jv) << std::endl; + Rethrow(); + } + + jt.jv[sfSponsor.jsonName][sfAccount.jsonName] = signer.acct.human(); + jt.jv[sfSponsor.jsonName][sfSigningPubKey.jsonName] = + strHex(signer.sig.pk().slice()); + + Serializer ss; + ss.add32(HashPrefix::txSign); + parse(jt.jv).addWithoutSigningFields(ss); + auto const sig = + ripple::sign(signer.acct.pk(), signer.acct.sk(), ss.slice()); + jt.jv[sfSponsor.jsonName][jss::TxnSignature] = + strHex(Slice{sig.data(), sig.size()}); +} + +void +msig::operator()(Env& env, JTx& jt) const +{ + auto const mySigners = signers; + jt.signer = [mySigners, &env](Env&, JTx& jtx) { + jtx[sfSponsor.getJsonName()][sfSigningPubKey.getJsonName()] = ""; + std::optional st; + try + { + st = parse(jtx.jv); + } + catch (parse_error const&) + { + env.test.log << pretty(jtx.jv) << std::endl; + Rethrow(); + } + auto& js = jtx[sfSponsor.getJsonName()][sfSigners.getJsonName()]; + for (std::size_t i = 0; i < mySigners.size(); ++i) + { + auto const& e = mySigners[i]; + auto& jo = js[i][sfSigner.getJsonName()]; + jo[jss::Account] = e.acct.human(); + jo[jss::SigningPubKey] = strHex(e.sig.pk().slice()); + + Serializer ss{buildMultiSigningData(*st, e.acct.id())}; + auto const sig = ripple::sign( + *publicKeyType(e.sig.pk().slice()), e.sig.sk(), ss.slice()); + jo[sfTxnSignature.getJsonName()] = + strHex(Slice{sig.data(), sig.size()}); + } + }; +} + +} // namespace sponsor +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index fc904f9e87..5878ebcad9 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -85,6 +85,57 @@ public: operator()(Env& env) const; }; +/** Match the number of items in the account's owner directory */ +class sponsored_owners +{ +private: + Account account_; + std::uint32_t value_; + +public: + sponsored_owners(Account const& account, std::uint32_t value) + : account_(account), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** Match the number of items in the account's owner directory */ +class sponsoring_count +{ +private: + Account account_; + std::uint32_t value_; + +public: + sponsoring_count(Account const& account, std::uint32_t value) + : account_(account), value_(value) + { + } + + void + operator()(Env& env) const; +}; + +/** Match the number of items in the account's owner directory */ +class sponsoring_account_count +{ +private: + Account account_; + std::uint32_t value_; + +public: + sponsoring_account_count(Account const& account, std::uint32_t value) + : account_(account), value_(value) + { + } + + void + operator()(Env& env) const; +}; + /** Match the number of trust lines in the account's owner directory */ using lines = owner_count; diff --git a/src/test/jtx/sponsor.h b/src/test/jtx/sponsor.h new file mode 100644 index 0000000000..ec0c813158 --- /dev/null +++ b/src/test/jtx/sponsor.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include +#include + +#include "test/jtx/SignerUtils.h" + +namespace ripple { +namespace test { +namespace jtx { + +namespace sponsor { + +// Json::Value +// transferAccount(jtx::Account const& account); + +// Json::Value +// transferObject(uint256 const& id); + +struct as +{ +private: + jtx::Account sponsor_; + std::uint32_t flags; + +public: + as(jtx::Account const& account, std::uint32_t flags = 0) + : sponsor_(account), flags(flags) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +struct sig +{ +private: + Reg signer; + +public: + sig(Reg signer_) : signer(std::move(signer_)) + { + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +struct msig +{ +private: + std::vector signers; + +public: + msig(std::vector signers_) : signers(std::move(signers_)) + { + sortSigners(signers); + } + + void + operator()(jtx::Env&, jtx::JTx& jtx) const; +}; + +} // namespace sponsor +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/xrpld/app/misc/CredentialHelpers.cpp b/src/xrpld/app/misc/CredentialHelpers.cpp index 6d1f9f78c5..caccefade4 100644 --- a/src/xrpld/app/misc/CredentialHelpers.cpp +++ b/src/xrpld/app/misc/CredentialHelpers.cpp @@ -93,7 +93,11 @@ deleteSLE( } // LCOV_EXCL_STOP if (isOwner) - adjustOwnerCount(view, sleAccount, -1, j); + { + auto const sponsor = + getLedgerEntryReserveSponsor(view, sleCredential); + adjustOwnerCount(view, sleAccount, sponsor, -1, j); + } return tesSUCCESS; }; diff --git a/src/xrpld/app/tx/detail/CancelCheck.cpp b/src/xrpld/app/tx/detail/CancelCheck.cpp index cfa3bd10e2..30e0213d8b 100644 --- a/src/xrpld/app/tx/detail/CancelCheck.cpp +++ b/src/xrpld/app/tx/detail/CancelCheck.cpp @@ -123,7 +123,8 @@ CancelCheck::doApply() // If we succeeded, update the check owner's reserve. auto const sleSrc = view().peek(keylet::account(srcId)); - adjustOwnerCount(view(), sleSrc, -1, viewJ); + auto const sponsor = getLedgerEntryReserveSponsor(view(), sleCheck); + adjustOwnerCount(view(), sleSrc, sponsor, -1, viewJ); // Remove check from ledger. view().erase(sleCheck); diff --git a/src/xrpld/app/tx/detail/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp index 0f1d08689c..c2c3f37259 100644 --- a/src/xrpld/app/tx/detail/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -508,7 +508,8 @@ CashCheck::doApply() } // If we succeeded, update the check owner's reserve. - adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ); + auto const sponsor = getLedgerEntryReserveSponsor(psb, sleCheck); + adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), sponsor, -1, viewJ); // Remove check from ledger. psb.erase(sleCheck); diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index 1392d84c08..28d2bcb340 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -213,11 +213,19 @@ Change::activateTrustLinesToSelfFix() if (tl->getFlags() & lsfLowReserve) adjustOwnerCount( - sb, sb.peek(keylet::account(lo.getIssuer())), -1, j_); + sb, + sb.peek(keylet::account(lo.getIssuer())), + std::nullopt, + -1, + j_); if (tl->getFlags() & lsfHighReserve) adjustOwnerCount( - sb, sb.peek(keylet::account(hi.getIssuer())), -1, j_); + sb, + sb.peek(keylet::account(hi.getIssuer())), + std::nullopt, + -1, + j_); sb.erase(tl); diff --git a/src/xrpld/app/tx/detail/CreateCheck.cpp b/src/xrpld/app/tx/detail/CreateCheck.cpp index 9baceef944..fbc084fd1c 100644 --- a/src/xrpld/app/tx/detail/CreateCheck.cpp +++ b/src/xrpld/app/tx/detail/CreateCheck.cpp @@ -243,7 +243,9 @@ CreateCheck::doApply() sleCheck->setFieldU64(sfOwnerNode, *page); } // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sle, 1, viewJ); + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + adjustOwnerCount(view(), sle, sponsor, 1, viewJ); + addSponsorToLedgerEntry(sleCheck, sponsor); return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 3cfae92cbd..fc1bcda26b 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -848,7 +848,8 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) } // Update owner count. - adjustOwnerCount(sb, sleCreator, 1, viewJ); + auto const sponsor = getTxReserveSponsor(sb, ctx_.tx); + adjustOwnerCount(sb, sleCreator, sponsor, 1, viewJ); JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue()) << " : " << to_string(saTakerGets.issue()) @@ -908,6 +909,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) sleOffer->setFlag(lsfSell); if (domainID) sleOffer->setFieldH256(sfDomainID, *domainID); + addSponsorToLedgerEntry(sleOffer, sponsor); // if it's a hybrid offer, set hybrid flag, and create an open dir if (bHybrid) diff --git a/src/xrpld/app/tx/detail/CreateTicket.cpp b/src/xrpld/app/tx/detail/CreateTicket.cpp index 1d718777e7..00c5a6c7b8 100644 --- a/src/xrpld/app/tx/detail/CreateTicket.cpp +++ b/src/xrpld/app/tx/detail/CreateTicket.cpp @@ -113,6 +113,7 @@ CreateTicket::doApply() txSeq != 0 && txSeq != (firstTicketSeq - 1)) return tefINTERNAL; + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); for (std::uint32_t i = 0; i < ticketCount; ++i) { std::uint32_t const curTicketSeq = firstTicketSeq + i; @@ -122,12 +123,6 @@ CreateTicket::doApply() sleTicket->setAccountID(sfAccount, account_); sleTicket->setFieldU32(sfTicketSequence, curTicketSeq); - if (ctx_.tx.isFieldPresent(sfSponsor) && - (ctx_.tx.getFieldObject(sfSponsor)[sfFlags] & tfSponsorReserve)) - { - sleTicket->setAccountID( - sfSponsorAccount, ctx_.tx.getFieldObject(sfSponsor)[sfAccount]); - } view().insert(sleTicket); auto const page = view().dirInsert( @@ -142,6 +137,7 @@ CreateTicket::doApply() return tecDIR_FULL; sleTicket->setFieldU64(sfOwnerNode, *page); + addSponsorToLedgerEntry(sleTicket, sponsor); } // Update the record of the number of Tickets this account owns. @@ -151,22 +147,7 @@ CreateTicket::doApply() sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount); // Every added Ticket counts against the creator's reserve. - adjustOwnerCount(view(), sleAccountRoot, ticketCount, viewJ); - - // Check reserve sponsorship - if (ctx_.tx.isFieldPresent(sfSponsor) && - (ctx_.tx.getFieldObject(sfSponsor)[sfFlags] & tfSponsorReserve)) - { - AccountID const& sponsor = ctx_.tx.getFieldObject(sfSponsor)[sfAccount]; - auto const& sponsorSle = view().peek(keylet::account(sponsor)); - sponsorSle->setFieldU32( - sfSponsoringOwnerCount, - sponsorSle->getFieldU32(sfSponsoringOwnerCount) + ticketCount); - sleAccountRoot->setFieldU32( - sfSponsoredOwnerCount, - sleAccountRoot->getFieldU32(sfSponsoredOwnerCount) + ticketCount); - view().update(sponsorSle); - } + adjustOwnerCount(view(), sleAccountRoot, sponsor, ticketCount, viewJ); // TicketCreate is the only transaction that can cause an account root's // Sequence field to increase by more than one. October 2018. diff --git a/src/xrpld/app/tx/detail/Credentials.cpp b/src/xrpld/app/tx/detail/Credentials.cpp index 73c397cf37..36e4ebbc73 100644 --- a/src/xrpld/app/tx/detail/Credentials.cpp +++ b/src/xrpld/app/tx/detail/Credentials.cpp @@ -174,7 +174,9 @@ CredentialCreate::doApply() return tecDIR_FULL; sleCred->setFieldU64(sfIssuerNode, *page); - adjustOwnerCount(view(), sleIssuer, 1, j_); + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + adjustOwnerCount(view(), sleIssuer, sponsor, 1, j_); + addSponsorToLedgerEntry(sleCred, sponsor); } if (subject == account_) @@ -392,8 +394,11 @@ CredentialAccept::doApply() sleCred->setFieldU32(sfFlags, lsfAccepted); view().update(sleCred); - adjustOwnerCount(view(), sleIssuer, -1, j_); - adjustOwnerCount(view(), sleSubject, 1, j_); + auto const currentSponsor = getTxReserveSponsor(view(), ctx_.tx); + adjustOwnerCount(view(), sleIssuer, currentSponsor, -1, j_); + auto const newSponsor = getTxReserveSponsor(view(), ctx_.tx); + adjustOwnerCount(view(), sleSubject, newSponsor, 1, j_); + addSponsorToLedgerEntry(sleCred, newSponsor); return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/DID.cpp b/src/xrpld/app/tx/detail/DID.cpp index 31ce7c8770..f8dd9619d8 100644 --- a/src/xrpld/app/tx/detail/DID.cpp +++ b/src/xrpld/app/tx/detail/DID.cpp @@ -108,7 +108,9 @@ addSLE( return tecDIR_FULL; (*sle)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal); + auto const sponsor = getTxReserveSponsor(ctx.view(), ctx.tx); + adjustOwnerCount(ctx.view(), sleAccount, sponsor, 1, ctx.journal); + addSponsorToLedgerEntry(sle, sponsor); ctx.view().update(sleAccount); return tesSUCCESS; @@ -215,7 +217,8 @@ DIDDelete::deleteSLE( if (!sleOwner) return tecINTERNAL; - adjustOwnerCount(view, sleOwner, -1, j); + auto const sponsor = getLedgerEntryReserveSponsor(view, sle); + adjustOwnerCount(view, sleOwner, sponsor, -1, j); view.update(sleOwner); // Remove object from ledger diff --git a/src/xrpld/app/tx/detail/DelegateSet.cpp b/src/xrpld/app/tx/detail/DelegateSet.cpp index 34e1c3afd3..a98e3d5951 100644 --- a/src/xrpld/app/tx/detail/DelegateSet.cpp +++ b/src/xrpld/app/tx/detail/DelegateSet.cpp @@ -123,7 +123,9 @@ DelegateSet::doApply() (*sle)[sfOwnerNode] = *page; ctx_.view().insert(sle); - adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + adjustOwnerCount(ctx_.view(), sleOwner, sponsor, 1, ctx_.journal); + addSponsorToLedgerEntry(sle, sponsor); } return tesSUCCESS; @@ -152,11 +154,12 @@ DelegateSet::deleteDelegate( if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleOwner, -1, j); + auto const sponsor = getLedgerEntryReserveSponsor(view, sle); + adjustOwnerCount(view, sleOwner, sponsor, -1, j); view.erase(sle); return tesSUCCESS; } -} // namespace ripple \ No newline at end of file +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/DeleteOracle.cpp b/src/xrpld/app/tx/detail/DeleteOracle.cpp index 78e3d55230..206ed662f4 100644 --- a/src/xrpld/app/tx/detail/DeleteOracle.cpp +++ b/src/xrpld/app/tx/detail/DeleteOracle.cpp @@ -94,7 +94,8 @@ DeleteOracle::deleteOracle( auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1; - adjustOwnerCount(view, sleOwner, count, j); + auto const sponsor = getLedgerEntryReserveSponsor(view, sle); + adjustOwnerCount(view, sleOwner, sponsor, count, j); view.erase(sle); diff --git a/src/xrpld/app/tx/detail/DepositPreauth.cpp b/src/xrpld/app/tx/detail/DepositPreauth.cpp index f10f09b38f..31fc1b866b 100644 --- a/src/xrpld/app/tx/detail/DepositPreauth.cpp +++ b/src/xrpld/app/tx/detail/DepositPreauth.cpp @@ -206,7 +206,9 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sleOwner, 1, j_); + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + adjustOwnerCount(view(), sleOwner, sponsor, 1, j_); + addSponsorToLedgerEntry(slePreauth, sponsor); } else if (ctx_.tx.isFieldPresent(sfUnauthorize)) { @@ -270,7 +272,9 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), sleOwner, 1, j_); + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + adjustOwnerCount(view(), sleOwner, sponsor, 1, j_); + addSponsorToLedgerEntry(slePreauth, sponsor); } else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials)) { @@ -311,7 +315,8 @@ DepositPreauth::removeFromLedger( if (!sleOwner) return tefINTERNAL; - adjustOwnerCount(view, sleOwner, -1, j); + auto const sponsor = getLedgerEntryReserveSponsor(view, slePreauth); + adjustOwnerCount(view, sleOwner, sponsor, -1, j); // Remove DepositPreauth from ledger. view.erase(slePreauth); diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index dd0ffac778..633841acfd 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -599,7 +599,9 @@ EscrowCreate::doApply() } // increment owner count - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + adjustOwnerCount(ctx_.view(), sle, sponsor, 1, ctx_.journal); + addSponsorToLedgerEntry(slep, sponsor); ctx_.view().update(sle); return tesSUCCESS; } @@ -796,6 +798,7 @@ template static TER escrowUnlockApplyHelper( ApplyView& view, + STTx const& tx, Rate lockedRate, std::shared_ptr const& sleDest, STAmount const& xrpBalance, @@ -810,6 +813,7 @@ template <> TER escrowUnlockApplyHelper( ApplyView& view, + STTx const& tx, Rate lockedRate, std::shared_ptr const& sleDest, STAmount const& xrpBalance, @@ -945,6 +949,7 @@ template <> TER escrowUnlockApplyHelper( ApplyView& view, + STTx const& tx, Rate lockedRate, std::shared_ptr const& sleDest, STAmount const& xrpBalance, @@ -977,7 +982,9 @@ escrowUnlockApplyHelper( } // update owner count. - adjustOwnerCount(view, sleDest, 1, journal); + auto const sponsor = getTxReserveSponsor(view, tx); + adjustOwnerCount(view, sleDest, sponsor, 1, journal); + addSponsorToLedgerEntry(sleDest, sponsor); } if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && @@ -1157,6 +1164,7 @@ EscrowFinish::doApply() [&](T const&) { return escrowUnlockApplyHelper( ctx_.view(), + ctx_.tx, lockedRate, sled, mPriorBalance, @@ -1187,7 +1195,8 @@ EscrowFinish::doApply() // Adjust source owner count auto const sle = ctx_.view().peek(keylet::account(account)); - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); + auto const sponsor = getLedgerEntryReserveSponsor(ctx_.view(), slep); + adjustOwnerCount(ctx_.view(), sle, sponsor, -1, ctx_.journal); ctx_.view().update(sle); // Remove escrow from ledger @@ -1372,6 +1381,7 @@ EscrowCancel::doApply() [&](T const&) { return escrowUnlockApplyHelper( ctx_.view(), + ctx_.tx, parityRate, slep, mPriorBalance, @@ -1398,7 +1408,8 @@ EscrowCancel::doApply() } } - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); + auto const sponsor = getLedgerEntryReserveSponsor(ctx_.view(), slep); + adjustOwnerCount(ctx_.view(), sle, sponsor, -1, ctx_.journal); ctx_.view().update(sle); // Remove escrow from ledger diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp index 77b21b65f3..7ab27891b2 100644 --- a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp @@ -178,6 +178,7 @@ MPTokenAuthorize::createMPToken( TER MPTokenAuthorize::authorize( ApplyView& view, + STTx const& tx, beast::Journal journal, MPTAuthorizeArgs const& args) { @@ -208,7 +209,8 @@ MPTokenAuthorize::authorize( false)) return tecINTERNAL; // LCOV_EXCL_LINE - adjustOwnerCount(view, sleAcct, -1, journal); + auto const sponsor = getLedgerEntryReserveSponsor(view, sleMpt); + adjustOwnerCount(view, sleAcct, sponsor, -1, journal); view.erase(sleMpt); return tesSUCCESS; @@ -243,7 +245,9 @@ MPTokenAuthorize::authorize( view.insert(mptoken); // Update owner count. - adjustOwnerCount(view, sleAcct, 1, journal); + auto const sponsor = getTxReserveSponsor(view, tx); + adjustOwnerCount(view, sleAcct, sponsor, 1, journal); + addSponsorToLedgerEntry(mptoken, sponsor); return tesSUCCESS; } @@ -289,6 +293,7 @@ MPTokenAuthorize::doApply() auto const& tx = ctx_.tx; return authorize( ctx_.view(), + tx, ctx_.journal, {.priorBalance = mPriorBalance, .mptIssuanceID = tx[sfMPTokenIssuanceID], diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.h b/src/xrpld/app/tx/detail/MPTokenAuthorize.h index a81dc7dea2..8fc4e9a597 100644 --- a/src/xrpld/app/tx/detail/MPTokenAuthorize.h +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.h @@ -51,6 +51,7 @@ public: static TER authorize( ApplyView& view, + STTx const& tx, beast::Journal journal, MPTAuthorizeArgs const& args); diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index da3b57c8fe..2db460076e 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -85,6 +85,7 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) Expected MPTokenIssuanceCreate::create( ApplyView& view, + STTx const& tx, beast::Journal journal, MPTCreateArgs const& args) { @@ -100,6 +101,8 @@ MPTokenIssuanceCreate::create( auto const mptId = makeMptID(args.sequence, args.account); auto const mptIssuanceKeylet = keylet::mptIssuance(mptId); + auto const sponsor = getTxReserveSponsor(view, tx); + // create the MPTokenIssuance { auto const ownerNode = view.dirInsert( @@ -132,11 +135,13 @@ MPTokenIssuanceCreate::create( if (args.domainId) (*mptIssuance)[sfDomainID] = *args.domainId; + addSponsorToLedgerEntry(mptIssuance, sponsor); + view.insert(mptIssuance); } // Update owner count. - adjustOwnerCount(view, acct, 1, journal); + adjustOwnerCount(view, acct, sponsor, 1, journal); return mptId; } @@ -147,6 +152,7 @@ MPTokenIssuanceCreate::doApply() auto const& tx = ctx_.tx; auto const result = create( view(), + tx, j_, { .priorBalance = mPriorBalance, diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h index ea01908dff..34cf8124ec 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h @@ -56,7 +56,11 @@ public: doApply() override; static Expected - create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args); + create( + ApplyView& view, + STTx const& tx, + beast::Journal journal, + MPTCreateArgs const& args); }; } // namespace ripple diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp index e2b87dbd79..d46b869ccf 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp @@ -78,7 +78,9 @@ MPTokenIssuanceDestroy::doApply() view().erase(mpt); - adjustOwnerCount(view(), view().peek(keylet::account(account_)), -1, j_); + auto const sponsor = getLedgerEntryReserveSponsor(view(), mpt); + adjustOwnerCount( + view(), view().peek(keylet::account(account_)), sponsor, -1, j_); return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp index 8e1a026415..c258902490 100644 --- a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp @@ -93,6 +93,7 @@ NFTokenCreateOffer::doApply() // Use implementation shared with NFTokenMint return nft::tokenOfferCreateApply( view(), + ctx_.tx, ctx_.tx[sfAccount], ctx_.tx[sfAmount], ctx_.tx[~sfDestination], diff --git a/src/xrpld/app/tx/detail/NFTokenMint.cpp b/src/xrpld/app/tx/detail/NFTokenMint.cpp index 42b551b3a4..f139c40aed 100644 --- a/src/xrpld/app/tx/detail/NFTokenMint.cpp +++ b/src/xrpld/app/tx/detail/NFTokenMint.cpp @@ -331,6 +331,7 @@ NFTokenMint::doApply() // because a Mint is only allowed to create a sell offer. if (TER const ter = nft::tokenOfferCreateApply( view(), + ctx_.tx, ctx_.tx[sfAccount], ctx_.tx[sfAmount], ctx_.tx[~sfDestination], diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.cpp b/src/xrpld/app/tx/detail/NFTokenUtils.cpp index 4866a3b385..f2b2e12940 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -294,6 +294,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft) adjustOwnerCount( view, view.peek(keylet::account(owner)), + std::nullopt, 1, beast::Journal{beast::Journal::getNullSink()}); }); @@ -458,11 +459,15 @@ removeToken( cnt--; if (cnt != 0) + { + auto const sponsor = getLedgerEntryReserveSponsor(view, curr); adjustOwnerCount( view, view.peek(keylet::account(owner)), + sponsor, cnt, beast::Journal{beast::Journal::getNullSink()}); + } return tesSUCCESS; } @@ -496,9 +501,11 @@ removeToken( curr->makeFieldAbsent(sfPreviousPageMin); } + auto const sponsor = getLedgerEntryReserveSponsor(view, curr); adjustOwnerCount( view, view.peek(keylet::account(owner)), + sponsor, -1, beast::Journal{beast::Journal::getNullSink()}); @@ -550,6 +557,7 @@ removeToken( adjustOwnerCount( view, view.peek(keylet::account(owner)), + std::nullopt, -1 * cnt, beast::Journal{beast::Journal::getNullSink()}); @@ -703,9 +711,11 @@ deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer) false)) return false; + auto const sponsor = getLedgerEntryReserveSponsor(view, offer); adjustOwnerCount( view, view.peek(keylet::account(owner)), + sponsor, -1, beast::Journal{beast::Journal::getNullSink()}); @@ -1022,6 +1032,7 @@ tokenOfferCreatePreclaim( TER tokenOfferCreateApply( ApplyView& view, + STTx const& tx, AccountID const& acctID, STAmount const& amount, std::optional const& dest, @@ -1038,6 +1049,7 @@ tokenOfferCreateApply( return tecINSUFFICIENT_RESERVE; auto const offerID = keylet::nftoffer(acctID, seqProxy.value()); + auto const sponsor = getTxReserveSponsor(view, tx); // Create the offer: { @@ -1084,11 +1096,13 @@ tokenOfferCreateApply( if (dest) (*offer)[sfDestination] = *dest; + addSponsorToLedgerEntry(offer, sponsor); + view.insert(offer); } // Update owner count. - adjustOwnerCount(view, view.peek(acctKeylet), 1, j); + adjustOwnerCount(view, view.peek(acctKeylet), sponsor, 1, j); return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.h b/src/xrpld/app/tx/detail/NFTokenUtils.h index 7ee0541984..e0e276db27 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.h +++ b/src/xrpld/app/tx/detail/NFTokenUtils.h @@ -142,6 +142,7 @@ tokenOfferCreatePreclaim( TER tokenOfferCreateApply( ApplyView& view, + STTx const& tx, AccountID const& acctID, STAmount const& amount, std::optional const& dest, diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index d9e53ac75c..abfc15a11e 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -155,7 +155,8 @@ closeChannel( "ripple::closeChannel : minimum channel amount"); (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance]; - adjustOwnerCount(view, sle, -1, j); + auto const sponsor = getLedgerEntryReserveSponsor(view, slep); + adjustOwnerCount(view, sle, sponsor, -1, j); view.update(sle); // Remove PayChan from ledger @@ -312,7 +313,10 @@ PayChanCreate::doApply() // Deduct owner's balance, increment owner count (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + adjustOwnerCount(ctx_.view(), sle, sponsor, 1, ctx_.journal); + addSponsorToLedgerEntry(slep, sponsor); + ctx_.view().update(sle); return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 692e03109e..9e5bc93a8d 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -415,6 +415,14 @@ Payment::doApply() sleDst->setAccountID(sfAccount, dstAccountID); sleDst->setFieldU32(sfSequence, seqno); + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (sponsor.has_value()) + { + addSponsorToLedgerEntry(sleDst, sponsor); + sponsor.value()->setFieldU32(sfSponsoringAccountCount, 1); + view().update(sponsor.value()); + } + view().insert(sleDst); } else diff --git a/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp b/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp index 64c498b68b..d5ca411792 100644 --- a/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp +++ b/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp @@ -87,7 +87,8 @@ PermissionedDomainDelete::doApply() XRPL_ASSERT( ownerSle && ownerSle->getFieldU32(sfOwnerCount) > 0, "ripple::PermissionedDomainDelete::doApply : nonzero owner count"); - adjustOwnerCount(view(), ownerSle, -1, ctx_.journal); + auto const sponsor = getLedgerEntryReserveSponsor(view(), slePd); + adjustOwnerCount(view(), ownerSle, sponsor, -1, ctx_.journal); view().erase(slePd); return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp index 6e2df2a082..b80cf88504 100644 --- a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp +++ b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp @@ -142,7 +142,9 @@ PermissionedDomainSet::doApply() slePd->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - adjustOwnerCount(view(), ownerSle, 1, ctx_.journal); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + adjustOwnerCount(view(), ownerSle, sponsor, 1, ctx_.journal); + addSponsorToLedgerEntry(slePd, sponsor); view().insert(slePd); } diff --git a/src/xrpld/app/tx/detail/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp index 8559c3e7b9..7e7cff6e05 100644 --- a/src/xrpld/app/tx/detail/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -188,7 +188,8 @@ adjustOwnerCount(ApplyContext& ctx, int count) if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount]))) { - adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal); + adjustOwnerCount( + ctx.view(), sleAccount, std::nullopt, count, ctx.journal); return true; } diff --git a/src/xrpld/app/tx/detail/SetSignerList.cpp b/src/xrpld/app/tx/detail/SetSignerList.cpp index 4a1ee703a0..c3c7c2a354 100644 --- a/src/xrpld/app/tx/detail/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -231,9 +231,11 @@ removeSignersFromLedger( return tefBAD_LEDGER; } + auto const sponsor = getLedgerEntryReserveSponsor(view, signers); adjustOwnerCount( view, view.peek(accountKeylet), + sponsor, removeFromOwnerCount, app.journal("View")); @@ -394,7 +396,9 @@ SetSignerList::replaceSignerList() // If we succeeded, the new entry counts against the // creator's reserve. - adjustOwnerCount(view(), sle, addedOwnerCount, viewJ); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + adjustOwnerCount(view(), sle, sponsor, addedOwnerCount, viewJ); + addSponsorToLedgerEntry(signerList, sponsor); return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index d3b39aaf11..ef7061bb2e 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -636,7 +636,7 @@ SetTrust::doApply() if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. - adjustOwnerCount(view(), sleLowAccount, 1, viewJ); + adjustOwnerCount(view(), sleLowAccount, std::nullopt, 1, viewJ); uFlagsOut |= lsfLowReserve; if (!bHigh) @@ -646,14 +646,14 @@ SetTrust::doApply() if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. - adjustOwnerCount(view(), sleLowAccount, -1, viewJ); + adjustOwnerCount(view(), sleLowAccount, std::nullopt, -1, viewJ); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. - adjustOwnerCount(view(), sleHighAccount, 1, viewJ); + adjustOwnerCount(view(), sleHighAccount, std::nullopt, 1, viewJ); uFlagsOut |= lsfHighReserve; if (bHigh) @@ -663,7 +663,7 @@ SetTrust::doApply() if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. - adjustOwnerCount(view(), sleHighAccount, -1, viewJ); + adjustOwnerCount(view(), sleHighAccount, std::nullopt, -1, viewJ); uFlagsOut &= ~lsfHighReserve; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 6278478408..f9dee8e63c 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -164,19 +164,19 @@ preflight1(PreflightContext const& ctx) auto const sponsor = ctx.tx.getFieldObject(sfSponsor); if (sponsor[sfAccount] == ctx.tx[sfAccount]) { - JLOG(ctx.j.debug()) << "preflight1: invalid sponsor account"; + JLOG(ctx.j.fatal()) << "preflight1: invalid sponsor account"; return temMALFORMED; } if (!(sponsor[sfFlags] & tfSponsorFee) && !(sponsor[sfFlags] & tfSponsorReserve)) { - JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags"; + JLOG(ctx.j.fatal()) << "preflight1: invalid sponsor flags"; return temMALFORMED; } - if (!sponsor.isFieldPresent(sfSignature) && + if (!sponsor.isFieldPresent(sfTxnSignature) && !sponsor.isFieldPresent(sfSigners)) { - JLOG(ctx.j.debug()) << "preflight1: no sfSignature or sfSigners"; + JLOG(ctx.j.fatal()) << "preflight1: no sfTxnSignature or sfSigners"; return temMALFORMED; } } @@ -558,7 +558,8 @@ Transactor::ticketDelete( } // Update the Ticket owner's reserve. - adjustOwnerCount(view, sleAccount, -1, j); + auto const sponsor = getLedgerEntryReserveSponsor(view, sleTicket); + adjustOwnerCount(view, sleAccount, sponsor, -1, j); // Remove Ticket from ledger. view.erase(sleTicket); @@ -645,7 +646,7 @@ Transactor::checkSign(PreclaimContext const& ctx) STArray const& txSigners(ctx.tx.getFieldArray(sfSigners)); return checkMultiSign(ctx.view, idAccount, txSigners, ctx.flags, ctx.j); } - + // if (ctx.tx.isFieldPresent(sfSponsor)) // { // // TODO: check the sponsor signature diff --git a/src/xrpld/app/tx/detail/VaultCreate.cpp b/src/xrpld/app/tx/detail/VaultCreate.cpp index cb6a994e7e..09c9c9dd3e 100644 --- a/src/xrpld/app/tx/detail/VaultCreate.cpp +++ b/src/xrpld/app/tx/detail/VaultCreate.cpp @@ -174,7 +174,9 @@ VaultCreate::doApply() if (auto ter = dirLink(view(), account_, vault)) return ter; - adjustOwnerCount(view(), owner, 1, j_); + auto const sponsor = getTxReserveSponsor(view(), tx); + adjustOwnerCount(view(), owner, sponsor, 1, j_); + addSponsorToLedgerEntry(vault, sponsor); auto ownerCount = owner->at(sfOwnerCount); if (mPriorBalance < view().fees().accountReserve(ownerCount)) return tecINSUFFICIENT_RESERVE; @@ -203,6 +205,7 @@ VaultCreate::doApply() // in the vault auto maybeShare = MPTokenIssuanceCreate::create( view(), + tx, j_, { .priorBalance = std::nullopt, diff --git a/src/xrpld/app/tx/detail/VaultDelete.cpp b/src/xrpld/app/tx/detail/VaultDelete.cpp index 7861e9e9b6..f99c5f3b62 100644 --- a/src/xrpld/app/tx/detail/VaultDelete.cpp +++ b/src/xrpld/app/tx/detail/VaultDelete.cpp @@ -112,7 +112,8 @@ VaultDelete::doApply() // Destroy the asset holding. auto asset = vault->at(sfAsset); - if (auto ter = removeEmptyHolding(view(), vault->at(sfAccount), asset, j_); + if (auto ter = removeEmptyHolding( + view(), ctx_.tx, vault->at(sfAccount), asset, j_); !isTesSuccess(ter)) return ter; @@ -145,7 +146,8 @@ VaultDelete::doApply() return tefBAD_LEDGER; // LCOV_EXCL_STOP } - adjustOwnerCount(view(), pseudoAcct, -1, j_); + auto const mptSponsor = getLedgerEntryReserveSponsor(view(), mpt); + adjustOwnerCount(view(), pseudoAcct, mptSponsor, -1, j_); view().erase(mpt); @@ -178,7 +180,8 @@ VaultDelete::doApply() return tefBAD_LEDGER; // LCOV_EXCL_STOP } - adjustOwnerCount(view(), owner, -1, j_); + auto const vaultSponsor = getLedgerEntryReserveSponsor(view(), vault); + adjustOwnerCount(view(), owner, vaultSponsor, -1, j_); // Destroy the vault. view().erase(vault); diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index 0efddb0ff7..3aae47a32b 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -200,7 +200,12 @@ VaultDeposit::doApply() if ((vault->getFlags() & tfVaultPrivate) && account_ != vault->at(sfOwner)) { if (auto const err = enforceMPTokenAuthorization( - ctx_.view(), mptIssuanceID, account_, mPriorBalance, j_); + ctx_.view(), + ctx_.tx, + mptIssuanceID, + account_, + mPriorBalance, + j_); !isTesSuccess(err)) return err; } @@ -212,6 +217,7 @@ VaultDeposit::doApply() { if (auto const err = MPTokenAuthorize::authorize( view(), + ctx_.tx, ctx_.journal, {.priorBalance = mPriorBalance, .mptIssuanceID = mptIssuanceID->value(), @@ -225,6 +231,7 @@ VaultDeposit::doApply() { if (auto const err = MPTokenAuthorize::authorize( view(), + ctx_.tx, ctx_.journal, { .priorBalance = mPriorBalance, diff --git a/src/xrpld/app/tx/detail/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp index 6ca049ee66..5c7a6dc0d6 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -753,7 +753,9 @@ finalizeClaimHelper( // Remove the claim id from the ledger outerSb.erase(sleClaimID); - adjustOwnerCount(outerSb, sleOwner, -1, j); + auto const sponsor = + getLedgerEntryReserveSponsor(outerSb, sleClaimID); + adjustOwnerCount(outerSb, sleOwner, sponsor, -1, j); } } @@ -981,6 +983,7 @@ TER applyCreateAccountAttestations( ApplyView& view, RawView& rawView, + STTx const& tx, TIter attBegin, TIter attEnd, AccountID const& doorAccount, @@ -1178,7 +1181,9 @@ applyCreateAccountAttestations( return tecINTERNAL; // Reserve was already checked - adjustOwnerCount(psb, sleDoor, 1, j); + auto const sponsor = getTxReserveSponsor(psb, tx); + adjustOwnerCount(psb, sleDoor, sponsor, 1, j); + addSponsorToLedgerEntry(createdSleClaimID, sponsor); psb.insert(createdSleClaimID); psb.update(sleDoor); } @@ -1360,6 +1365,7 @@ attestationDoApply(ApplyContext& ctx) return applyCreateAccountAttestations( ctx.view(), ctx.rawView(), + ctx.tx, &*att, &*att + 1, thisDoor, @@ -1547,7 +1553,9 @@ XChainCreateBridge::doApply() (*sleBridge)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + adjustOwnerCount(ctx_.view(), sleAcct, sponsor, 1, ctx_.journal); + addSponsorToLedgerEntry(sleBridge, sponsor); ctx_.view().insert(sleBridge); ctx_.view().update(sleAcct); @@ -2123,7 +2131,9 @@ XChainCreateClaimID::doApply() (*sleClaimID)[sfOwnerNode] = *page; } - adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + adjustOwnerCount(ctx_.view(), sleAcct, sponsor, 1, ctx_.journal); + addSponsorToLedgerEntry(sleClaimID, sponsor); ctx_.view().insert(sleClaimID); ctx_.view().update(sleBridge); diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index c0cc251576..78c4d263eb 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -452,6 +452,17 @@ areCompatible( beast::Journal::Stream& s, char const* reason); +std::optional> +getTxReserveSponsor(ApplyView& view, STTx const& tx); + +std::optional> +getLedgerEntryReserveSponsor(ApplyView& view, SLE::ref sle); + +void +addSponsorToLedgerEntry( + std::shared_ptr const& sle, + std::optional> const& sponsorSle); + //------------------------------------------------------------------------------ // // Modifiers @@ -462,7 +473,8 @@ areCompatible( void adjustOwnerCount( ApplyView& view, - std::shared_ptr const& sle, + std::shared_ptr const& accountSle, + std::optional> const& sponsorSle, std::int32_t amount, beast::Journal j); @@ -470,11 +482,17 @@ inline void adjustOwnerCount( ApplyView& view, AccountID const& account, + std::optional const& sponsor, std::int32_t amount, beast::Journal j) { return adjustOwnerCount( - view, view.peek(keylet::account(account)), amount, j); + view, + view.peek(keylet::account(account)), + sponsor.has_value() ? view.peek(keylet::account(*sponsor)) + : std::optional>(), + amount, + j); } /** @{ */ @@ -590,6 +608,7 @@ addEmptyHolding( [[nodiscard]] TER addEmptyHolding( ApplyView& view, + STTx const& tx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -641,6 +660,7 @@ trustCreate( [[nodiscard]] TER removeEmptyHolding( ApplyView& view, + STTx const& tx, AccountID const& accountID, Issue const& issue, beast::Journal journal); @@ -648,6 +668,7 @@ removeEmptyHolding( [[nodiscard]] TER removeEmptyHolding( ApplyView& view, + STTx const& tx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal); @@ -655,13 +676,14 @@ removeEmptyHolding( [[nodiscard]] inline TER removeEmptyHolding( ApplyView& view, + STTx const& tx, AccountID const& accountID, Asset const& asset, beast::Journal journal) { return std::visit( [&](TIss const& issue) -> TER { - return removeEmptyHolding(view, accountID, issue, journal); + return removeEmptyHolding(view, tx, accountID, issue, journal); }, asset.value()); } @@ -863,6 +885,7 @@ requireAuth( [[nodiscard]] TER enforceMPTokenAuthorization( ApplyView& view, + STTx const& tx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 1f616ed491..36e647cca2 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -1021,6 +1021,40 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal) return std::nullopt; } +std::optional> +getTxReserveSponsor(ApplyView& view, STTx const& tx) +{ + if (tx.isFieldPresent(sfSponsor)) + { + auto const sponsorObj = tx.getFieldObject(sfSponsor); + auto const flags = sponsorObj.getFlags(); + auto const sponsorID = sponsorObj.getAccountID(sfAccount); + if (flags & tfSponsorReserve) + { + return view.peek(keylet::account(sponsorID)); + } + } + return std::nullopt; +} + +std::optional> +getLedgerEntryReserveSponsor(ApplyView& view, SLE::ref sle) +{ + if (sle->isFieldPresent(sfSponsorAccount)) + return view.peek(keylet::account(sle->getAccountID(sfSponsorAccount))); + return std::nullopt; +} + +void +addSponsorToLedgerEntry( + std::shared_ptr const& sle, + std::optional> const& sponsorSle) +{ + if (sponsorSle) + sle->setAccountID( + sfSponsorAccount, sponsorSle.value()->getAccountID(sfAccount)); +} + //------------------------------------------------------------------------------ // // Modifiers @@ -1030,19 +1064,48 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal) void adjustOwnerCount( ApplyView& view, - std::shared_ptr const& sle, + std::shared_ptr const& accountSle, + std::optional> const& sponsorSle, std::int32_t amount, beast::Journal j) { - if (!sle) + if (!accountSle) return; XRPL_ASSERT(amount, "ripple::adjustOwnerCount : nonzero amount input"); - std::uint32_t const current{sle->getFieldU32(sfOwnerCount)}; - AccountID const id = (*sle)[sfAccount]; + + if (sponsorSle) + { + XRPL_ASSERT(sponsorSle, "ripple::adjustOwnerCount : sponsor not found"); + { + // modify sponsor's SponsoringOwnerCount + std::uint32_t const current{ + sponsorSle.value()->getFieldU32(sfSponsoringOwnerCount)}; + AccountID const id = sponsorSle.value()->getAccountID(sfAccount); + std::uint32_t const adjusted = + confineOwnerCount(current, amount, id, j); + view.adjustOwnerCountHook(id, current, adjusted); + sponsorSle.value()->setFieldU32(sfSponsoringOwnerCount, adjusted); + view.update(sponsorSle.value()); + } + { + // modify account's SponsoredOwnerCount + std::uint32_t const current{ + accountSle->getFieldU32(sfSponsoredOwnerCount)}; + AccountID const id = (*accountSle)[sfAccount]; + std::uint32_t const adjusted = + confineOwnerCount(current, amount, id, j); + view.adjustOwnerCountHook(id, current, adjusted); + accountSle->setFieldU32(sfSponsoredOwnerCount, adjusted); + view.update(accountSle); + } + return; + } + std::uint32_t const current{accountSle->getFieldU32(sfOwnerCount)}; + AccountID const id = (*accountSle)[sfAccount]; std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j); view.adjustOwnerCountHook(id, current, adjusted); - sle->setFieldU32(sfOwnerCount, adjusted); - view.update(sle); + accountSle->setFieldU32(sfOwnerCount, adjusted); + view.update(accountSle); } std::function @@ -1201,6 +1264,7 @@ addEmptyHolding( [[nodiscard]] TER addEmptyHolding( ApplyView& view, + STTx const& tx, AccountID const& accountID, XRPAmount priorBalance, MPTIssue const& mptIssue, @@ -1217,6 +1281,7 @@ addEmptyHolding( return MPTokenAuthorize::authorize( view, + tx, journal, {.priorBalance = priorBalance, .mptIssuanceID = mptID, @@ -1330,7 +1395,7 @@ trustCreate( } sleRippleState->setFieldU32(sfFlags, uFlags); - adjustOwnerCount(view, sleAccount, 1, j); + adjustOwnerCount(view, sleAccount, std::nullopt, 1, j); // ONLY: Create ripple balance. sleRippleState->setFieldAmount( @@ -1345,6 +1410,7 @@ trustCreate( [[nodiscard]] TER removeEmptyHolding( ApplyView& view, + STTx const& tx, AccountID const& accountID, Issue const& issue, beast::Journal journal) @@ -1375,7 +1441,7 @@ removeEmptyHolding( view.peek(keylet::account(line->at(sfLowLimit)->getIssuer())); if (!sleLowAccount) return tecINTERNAL; - adjustOwnerCount(view, sleLowAccount, -1, journal); + adjustOwnerCount(view, sleLowAccount, std::nullopt, -1, journal); // It's not really necessary to clear the reserve flag, since the line // is about to be deleted, but this will make the metadata reflect an // accurate state at the time of deletion. @@ -1389,7 +1455,7 @@ removeEmptyHolding( view.peek(keylet::account(line->at(sfHighLimit)->getIssuer())); if (!sleHighAccount) return tecINTERNAL; - adjustOwnerCount(view, sleHighAccount, -1, journal); + adjustOwnerCount(view, sleHighAccount, std::nullopt, -1, journal); // It's not really necessary to clear the reserve flag, since the line // is about to be deleted, but this will make the metadata reflect an // accurate state at the time of deletion. @@ -1407,6 +1473,7 @@ removeEmptyHolding( [[nodiscard]] TER removeEmptyHolding( ApplyView& view, + STTx const& tx, AccountID const& accountID, MPTIssue const& mptIssue, beast::Journal journal) @@ -1420,6 +1487,7 @@ removeEmptyHolding( return MPTokenAuthorize::authorize( view, + tx, journal, {.priorBalance = {}, .mptIssuanceID = mptID, @@ -1517,7 +1585,8 @@ offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j) } } - adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j); + auto const sponsor = getLedgerEntryReserveSponsor(view, sle); + adjustOwnerCount(view, view.peek(keylet::account(owner)), sponsor, -1, j); view.erase(sle); @@ -1611,7 +1680,11 @@ rippleCreditIOU( { // Clear the reserve of the sender, possibly delete the line! adjustOwnerCount( - view, view.peek(keylet::account(uSenderID)), -1, j); + view, + view.peek(keylet::account(uSenderID)), + std::nullopt, + -1, + j); // Clear reserve flag. sleRippleState->setFieldU32( @@ -2067,7 +2140,7 @@ updateTrustLine( { // VFALCO Where is the line being deleted? // Clear the reserve of the sender, possibly delete the line! - adjustOwnerCount(view, sle, -1, j); + adjustOwnerCount(view, sle, std::nullopt, -1, j); // Clear reserve flag. state->setFieldU32( @@ -2418,6 +2491,7 @@ requireAuth( [[nodiscard]] TER enforceMPTokenAuthorization( ApplyView& view, + STTx const& tx, MPTID const& mptIssuanceID, AccountID const& account, XRPAmount const& priorBalance, // for MPToken authorization @@ -2499,6 +2573,7 @@ enforceMPTokenAuthorization( "ripple::enforceMPTokenAuthorization : new MPToken for domain"); if (auto const err = MPTokenAuthorize::authorize( view, + tx, j, { .priorBalance = priorBalance, @@ -2660,7 +2735,7 @@ deleteAMMTrustLine( if (!(sleState->getFlags() & uFlags)) return tecINTERNAL; - adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j); + adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, std::nullopt, -1, j); return tesSUCCESS; }