Compare commits

...

6 Commits

Author SHA1 Message Date
Mayukha Vadari
3f06277ffe Merge remote-tracking branch 'upstream/develop' into mvadari/rearch/sponsorship-entry
# Conflicts:
#	src/libxrpl/ledger/helpers/AccountRootHelpers.cpp
#	src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp
2026-10-12 12:55:37 -04:00
Mayukha Vadari
563b3a179e Document hasBudget parameters and return value 2026-10-05 15:46:46 +01:00
Mayukha Vadari
53984ccd50 Include Keylet.h in the SLEBase test
(cherry picked from commit 3cca9d3266)
2026-10-05 14:37:12 +01:00
Mayukha Vadari
e5010cb4f8 test: Drop extern template declarations from SLEBase test
An explicit instantiation declaration stops clang from emitting the
inline constructors of the header-only entries at -O0, so the debug
builds fail to link xrpl_tests (undefined AccountRootEntry constructors).
Naming each class in a static_assert already instantiates its definition,
which is all the exhaustiveness check needs.

(cherry picked from commit 611415e1f9)
2026-10-05 14:37:12 +01:00
Mayukha Vadari
447a4f8723 test: Declare, not define, entry instantiations in SLEBase test
The exhaustive per-entry-type check in the SLEBase gtest used explicit
instantiation definitions. Once an entry class gets out-of-line member
bodies in its own .cpp (with the explicit instantiation definition
there), the test would instantiate the class a second time, and MSVC
rejects it with C4661 because those member bodies are not visible in
the test translation unit. Explicit instantiation declarations still
instantiate the class definition, which is all the check needs.

(cherry picked from commit 8b07abc3cd)
2026-10-05 13:34:15 +01:00
Mayukha Vadari
10d64ef942 refactor: Use SponsorshipEntry everywhere
Replace the raw Sponsorship SLE reads, peeks, inserts, updates and
erases in SponsorshipSet, SponsorshipTransfer, Transactor and
AccountRootHelpers with SponsorshipEntryR/SponsorshipEntryW.

Move the Sponsorship helpers onto the wrapper:
totalRemainingOwnerCount and hasSponsorshipBudget (as hasBudget)
become const members, decrementPrefundedReserveCount becomes a
writable member, and deleteSponsorship becomes removeFromLedger().
Longer bodies live in the new SponsorshipEntry.cpp.

Pure refactor: no behavior change.
2026-10-05 13:34:15 +01:00
7 changed files with 242 additions and 161 deletions

View File

@@ -7,6 +7,12 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <optional>
namespace xrpl {
@@ -28,6 +34,66 @@ public:
: Base(keylet::sponsorship(sponsor, sponsee), view, j)
{
}
/**
* Computes the resulting RemainingOwnerCount using signed 64-bit
* arithmetic to avoid unsigned wraparound.
*
* A missing entry (object creation) or absent field counts as zero.
* Callers handle the out-of-range results: a negative value is clamped to
* zero (field absent) and overflow is rejected in preclaim.
*
* @param remainingOwnerCountDelta The requested change, if any
* @return The current RemainingOwnerCount plus the delta
*/
[[nodiscard]] std::int64_t
totalRemainingOwnerCount(std::optional<std::int32_t> const& remainingOwnerCountDelta) const
{
std::uint32_t const currentCount =
this->exists() ? (**this)[~sfRemainingOwnerCount].value_or(0u) : 0u;
return static_cast<std::int64_t>(currentCount) + remainingOwnerCountDelta.value_or(0);
}
/**
* Returns true if applying the deltas leaves the Sponsorship with a
* budget: a positive FeeAmount or a positive RemainingOwnerCount.
*
* A missing entry (object creation) requires both deltas, when present,
* to be positive.
*
* @param feeAmountDelta The requested FeeAmount change, if any
* @param remainingOwnerCountDelta The requested RemainingOwnerCount change, if any
* @return True if the resulting FeeAmount or RemainingOwnerCount would be
* positive; false otherwise
*/
[[nodiscard]] bool
hasBudget(
std::optional<STAmount> const& feeAmountDelta,
std::optional<std::int32_t> const& remainingOwnerCountDelta) const;
/**
* Consumes the sponsor's pre-funded reserve budget: lowers
* RemainingOwnerCount by delta and updates the entry.
*
* @param delta The number of owner reserves to consume
* @return tesSUCCESS, or tefINTERNAL if the count is too low
*/
[[nodiscard]] TER
decrementPrefundedReserveCount(std::uint32_t delta)
requires Base::kIsWritable;
/**
* Removes the Sponsorship from the sponsor's and sponsee's directories,
* decrements the sponsor's owner count, returns any pre-funded FeeAmount
* to the sponsor and erases the Sponsorship from the ledger.
*
* The sponsor and sponsee are read from sfOwner and sfSponsee.
*
* @return tesSUCCESS, or tecINTERNAL / tefBAD_LEDGER on failure
*/
[[nodiscard]] TER
removeFromLedger()
requires Base::kIsWritable;
};
using SponsorshipEntryR = SponsorshipEntry<ReadView>;

View File

@@ -2,8 +2,8 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SponsorshipEntry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -53,7 +53,7 @@ public:
private:
TER
createSponsorship(
Keylet const& sponsorshipKeylet,
SponsorshipEntryW& sponsorship,
AccountID const& sponsorID,
AccountID const& sponseeID,
SLE::Ref sponsorAccSle,

View File

@@ -0,0 +1,121 @@
#include <xrpl/ledger/entries/SponsorshipEntry.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <optional>
namespace xrpl {
template <typename ViewT>
bool
SponsorshipEntry<ViewT>::hasBudget(
std::optional<STAmount> const& feeAmountDelta,
std::optional<std::int32_t> const& remainingOwnerCountDelta) const
{
// sfFeeAmountDelta and sfRemainingOwnerCountDelta must be non-negative when creating a new
// Sponsorship object.
if (!this->exists())
{
if (feeAmountDelta.has_value() && *feeAmountDelta <= beast::kZero)
return false;
if (remainingOwnerCountDelta.has_value() && *remainingOwnerCountDelta <= 0)
return false;
}
// If the transaction omits a field, it keeps whatever the existing object holds,
// so fall back to the current SLE value when the tx does not set it.
STAmount const currentFee =
this->exists() ? (**this)[~sfFeeAmount].value_or(STAmount{0}) : STAmount{0};
STAmount const newFee = currentFee + feeAmountDelta.value_or(STAmount{0});
std::int64_t const newCount = totalRemainingOwnerCount(remainingOwnerCountDelta);
return newFee > beast::kZero || newCount > 0;
}
template <typename ViewT>
TER
SponsorshipEntry<ViewT>::decrementPrefundedReserveCount(std::uint32_t const delta)
requires Base::kIsWritable
{
if (delta == 0)
return tesSUCCESS; // LCOV_EXCL_LINE
auto const currentReserveCount = (*this)->getFieldU32(sfRemainingOwnerCount);
if (currentReserveCount < delta)
{
// LCOV_EXCL_START
// Already verified by checkReserve (sufficient RemainingOwnerCount)
UNREACHABLE("xrpl::decrementPrefundedReserveCount : invalid reserve count");
return tefINTERNAL;
// LCOV_EXCL_STOP
}
(*this)->at(sfRemainingOwnerCount) = currentReserveCount - delta;
this->update();
return tesSUCCESS;
}
template <typename ViewT>
TER
SponsorshipEntry<ViewT>::removeFromLedger()
requires Base::kIsWritable
{
if (!this->exists())
return tecINTERNAL; // LCOV_EXCL_LINE
auto& view = this->applyView();
auto const j = this->journal();
auto const sponsorID = (**this)[sfOwner];
auto const sponseeID = (**this)[sfSponsee];
// The sponsor owns the Sponsorship object, so deletion releases the
// sponsor's owner reserve.
auto const sponsorAccSle = view.peek(keylet::account(sponsorID));
if (!sponsorAccSle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(keylet::ownerDir(sponsorID), (**this)[sfOwnerNode], this->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsor.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
if (!view.dirRemove(keylet::ownerDir(sponseeID), (**this)[sfSponseeNode], this->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsee.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
decreaseOwnerCountForObject(view, sponsorAccSle, this->mutableRawSle(), 1, j);
// Return any prefunded fee amount to the sponsor before erasing the object.
if ((*this)->isFieldPresent(sfFeeAmount))
{
(*sponsorAccSle)[sfBalance] += (*this)->getFieldAmount(sfFeeAmount);
view.update(sponsorAccSle);
}
this->erase();
return tesSUCCESS;
}
template class SponsorshipEntry<ReadView>;
template class SponsorshipEntry<ApplyView>;
} // namespace xrpl

View File

@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SponsorshipEntry.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -181,15 +182,20 @@ adjustOwnerCountSigned(
view.adjustOwnerCountHook(sponsorID, sponsorCurrent, sponsorAdjustment);
}
auto const sponsorshipSle = view.peek(keylet::sponsorship(sponsorID, accountID));
if (sponsorshipSle && adjustment > 0)
SponsorshipEntryW sponsorship(sponsorID, accountID, view, j);
if (sponsorship && adjustment > 0)
{
// Only decrease the pre-funded ReserveCount on Sponsorship if we assign new
// objects. Removing/reassigning ownership of the object doesn't increase
// RemainingOwnerCount back. Don't call hook because this counter is not something
// that requires reserve (like other sf...OwnerCounts do).
adjustOwnerCountImpl(
view, sponsorshipSle, sfRemainingOwnerCount, sponsorID, -adjustment, j);
view,
sponsorship.mutableRawSle(),
sfRemainingOwnerCount,
sponsorID,
-adjustment,
j);
}
}
@@ -435,9 +441,8 @@ checkReserve(
if (sponsorSle->getType() != ltACCOUNT_ROOT)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sle = ctx.view.read(
keylet::sponsorship(
sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount)));
SponsorshipEntryR const sle(
sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount), ctx.view, j);
// A reserve-sponsored tx must carry a sponsor signature
// (cosigning path) and/or have a pre-existing sponsorship SLE

View File

@@ -11,6 +11,7 @@
#include <xrpl/json/to_string.h> // IWYU pragma: keep
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SponsorshipEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
@@ -430,8 +431,7 @@ Transactor::checkSponsor(ReadView const& view, STTx const& tx)
// If the transaction contains sfDelegate, the Sponsorship object should be
// between the sponsor and the delegate.
auto const sponsorshipSle =
view.read(keylet::sponsorship(tx.getAccountID(sfSponsor), tx.getInitiator()));
SponsorshipEntryR const sponsorshipSle(tx.getAccountID(sfSponsor), tx.getInitiator(), view);
// sponsorship object missing for pre-funded (no co-signing) tx
if (!sponsorshipSle)

View File

@@ -1,12 +1,12 @@
#include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SponsorshipEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
@@ -28,48 +28,6 @@
namespace xrpl {
// Compute the resulting RemainingOwnerCount using signed 64-bit arithmetic to
// avoid unsigned wraparound. A missing SLE (object creation) or absent field
// counts as zero. Callers handle the out-of-range results: a negative value is
// clamped to zero (field absent) and overflow is rejected in preclaim.
static std::int64_t
totalRemainingOwnerCount(
SLE::ConstRef sponsorshipSle,
std::optional<std::int32_t> const& remainingOwnerCountDelta)
{
std::uint32_t const currentCount =
sponsorshipSle ? (*sponsorshipSle)[~sfRemainingOwnerCount].value_or(0u) : 0u;
return static_cast<std::int64_t>(currentCount) + remainingOwnerCountDelta.value_or(0);
}
static bool
hasSponsorshipBudget(
SLE::ConstRef sponsorshipSle,
std::optional<STAmount> const& feeAmountDelta,
std::optional<std::int32_t> const& remainingOwnerCountDelta)
{
// sfFeeAmountDelta and sfRemainingOwnerCountDelta must be non-negative when creating a new
// Sponsorship object.
if (!sponsorshipSle)
{
if (feeAmountDelta.has_value() && *feeAmountDelta <= beast::kZero)
return false;
if (remainingOwnerCountDelta.has_value() && *remainingOwnerCountDelta <= 0)
return false;
}
// If the transaction omits a field, it keeps whatever the existing object holds,
// so fall back to the current SLE value when the tx does not set it.
STAmount const currentFee =
sponsorshipSle ? (*sponsorshipSle)[~sfFeeAmount].value_or(STAmount{0}) : STAmount{0};
STAmount const newFee = currentFee + feeAmountDelta.value_or(STAmount{0});
std::int64_t const newCount =
totalRemainingOwnerCount(sponsorshipSle, remainingOwnerCountDelta);
return newFee > beast::kZero || newCount > 0;
}
TxConsequences
SponsorshipSet::makeTxConsequences(PreflightContext const& ctx)
{
@@ -176,78 +134,33 @@ SponsorshipSet::preclaim(PreclaimContext const& ctx)
if (isPseudoAccount(sponsorAccSle) || isPseudoAccount(sponseeSle))
return tecPSEUDO_ACCOUNT;
auto const sponsorshipSle = ctx.view.read(keylet::sponsorship(sponsorID, sponseeID));
SponsorshipEntryR const sponsorship(sponsorID, sponseeID, ctx.view, ctx.j);
// Deleting a Sponsorship object requires the object to already exist.
if (ctx.tx.isFlag(tfDeleteObject) && !sponsorshipSle)
if (ctx.tx.isFlag(tfDeleteObject) && !sponsorship)
return tecNO_ENTRY;
if (!ctx.tx.isFlag(tfDeleteObject))
{
// Reject if applying the delta would overflow uint32_t. A negative delta
// that underflows is clamped to zero (field absent) rather than erroring.
if (totalRemainingOwnerCount(sponsorshipSle, ctx.tx[~sfRemainingOwnerCountDelta]) >
if (sponsorship.totalRemainingOwnerCount(ctx.tx[~sfRemainingOwnerCountDelta]) >
static_cast<std::int64_t>(std::numeric_limits<std::uint32_t>::max()))
return tecLIMIT_EXCEEDED;
// Reject creating or updating a Sponsorship that would be left with no
// budget (neither a positive FeeAmount nor a positive RemainingOwnerCount).
// Such an object is unusable yet still consumes the sponsor's reserve.
if (!hasSponsorshipBudget(
sponsorshipSle, ctx.tx[~sfFeeAmountDelta], ctx.tx[~sfRemainingOwnerCountDelta]))
if (!sponsorship.hasBudget(ctx.tx[~sfFeeAmountDelta], ctx.tx[~sfRemainingOwnerCountDelta]))
return tecNO_PERMISSION;
}
return tesSUCCESS;
}
static TER
deleteSponsorship(ApplyView& view, SLE::Ref sle, beast::Journal j)
{
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorID = (*sle)[sfOwner];
auto const sponseeID = (*sle)[sfSponsee];
// The sponsor owns the Sponsorship object, so deletion releases the
// sponsor's owner reserve.
auto const sponsorAccSle = view.peek(keylet::account(sponsorID));
if (!sponsorAccSle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(keylet::ownerDir(sponsorID), (*sle)[sfOwnerNode], sle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsor.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
if (!view.dirRemove(keylet::ownerDir(sponseeID), (*sle)[sfSponseeNode], sle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Sponsorship from sponsee.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
decreaseOwnerCountForObject(view, sponsorAccSle, sle, 1, j);
// Return any prefunded fee amount to the sponsor before erasing the object.
if (sle->isFieldPresent(sfFeeAmount))
{
(*sponsorAccSle)[sfBalance] += sle->getFieldAmount(sfFeeAmount);
view.update(sponsorAccSle);
}
view.erase(sle);
return tesSUCCESS;
}
TER
SponsorshipSet::createSponsorship(
Keylet const& sponsorshipKeylet,
SponsorshipEntryW& sponsorship,
AccountID const& sponsorID,
AccountID const& sponseeID,
SLE::Ref sponsorAccSle,
@@ -260,7 +173,7 @@ SponsorshipSet::createSponsorship(
bool const hasPositiveFeeAmount = feeAmountDelta.has_value() && *feeAmountDelta > beast::kZero;
// Create a new Sponsorship object between the sponsor and sponsee.
auto const newSle = std::make_shared<SLE>(sponsorshipKeylet);
sponsorship.newSLE();
STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance];
// sfFeeAmountDelta must be positive if the sponsorship object doesn't exist. This is
// checked in preclaim.
@@ -268,8 +181,8 @@ SponsorshipSet::createSponsorship(
!feeAmountDelta.has_value() || *feeAmountDelta > beast::kZero,
"xrpl::SponsorshipSet::doApply : new sponsorship has positive fee amount");
(*newSle)[sfOwner] = sponsorID;
(*newSle)[sfSponsee] = sponseeID;
(*sponsorship)[sfOwner] = sponsorID;
(*sponsorship)[sfSponsee] = sponseeID;
if (feeAmountDelta && feeAmountDelta->xrp() > sponsorBalanceAfterFee.xrp())
return tecUNFUNDED;
@@ -292,14 +205,14 @@ SponsorshipSet::createSponsorship(
if (hasPositiveFeeAmount)
{
// New object: FeeAmount starts absent, so deduct and record the full amount
(*newSle)[sfFeeAmount] = *feeAmountDelta;
(*sponsorship)[sfFeeAmount] = *feeAmountDelta;
(*sponsorAccSle)[sfBalance] -= *feeAmountDelta;
}
if (maxFee && *maxFee > beast::kZero)
(*newSle)[sfMaxFee] = *maxFee;
(*sponsorship)[sfMaxFee] = *maxFee;
if (remainingOwnerCountDelta && *remainingOwnerCountDelta > 0)
(*newSle)[sfRemainingOwnerCount] = *remainingOwnerCountDelta;
(*sponsorship)[sfRemainingOwnerCount] = *remainingOwnerCountDelta;
std::uint32_t flags = 0;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
@@ -308,25 +221,25 @@ SponsorshipSet::createSponsorship(
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
flags |= lsfSponsorshipRequireSignForReserve;
(*newSle)[sfFlags] = flags;
(*sponsorship)[sfFlags] = flags;
auto const sponsorPage = view().dirInsert(
keylet::ownerDir(sponsorID), sponsorshipKeylet, describeOwnerDir(sponsorID));
keylet::ownerDir(sponsorID), sponsorship.keylet(), describeOwnerDir(sponsorID));
if (!sponsorPage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfOwnerNode] = *sponsorPage;
(*sponsorship)[sfOwnerNode] = *sponsorPage;
auto const sponseePage = view().dirInsert(
keylet::ownerDir(sponseeID), sponsorshipKeylet, describeOwnerDir(sponseeID));
keylet::ownerDir(sponseeID), sponsorship.keylet(), describeOwnerDir(sponseeID));
if (!sponseePage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfSponseeNode] = *sponseePage;
(*sponsorship)[sfSponseeNode] = *sponseePage;
// NOLINTNEXTLINE(readability-suspicious-call-argument)
increaseOwnerCount(view(), sponsorAccSle, reserveSponsorAccSle, 1, ctx_.journal);
addSponsorToLedgerEntry(newSle, reserveSponsorAccSle);
addSponsorToLedgerEntry(sponsorship.mutableRawSle(), reserveSponsorAccSle);
ctx_.view().insert(newSle);
sponsorship.insert();
return tesSUCCESS;
}
@@ -346,15 +259,14 @@ SponsorshipSet::doApply()
if (!ctx_.view().exists(keylet::account(sponseeID)))
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID);
auto const sponsorshipSle = ctx_.view().peek(sponsorshipKeylet);
SponsorshipEntryW sponsorship(sponsorID, sponseeID, ctx_.view(), ctx_.journal);
if (ctx_.tx.isFlag(tfDeleteObject))
{
if (!sponsorshipSle)
if (!sponsorship)
return tecINTERNAL; // LCOV_EXCL_LINE
return deleteSponsorship(ctx_.view(), sponsorshipSle, ctx_.journal);
return sponsorship.removeFromLedger();
}
auto const feeAmountDelta = ctx_.tx[~sfFeeAmountDelta];
@@ -365,17 +277,17 @@ SponsorshipSet::doApply()
if (!reserveSponsorAccSle)
return reserveSponsorAccSle.error(); // LCOV_EXCL_LINE
if (!sponsorshipSle)
if (!sponsorship)
{
return createSponsorship(
sponsorshipKeylet, sponsorID, sponseeID, sponsorAccSle, *reserveSponsorAccSle);
sponsorship, sponsorID, sponseeID, sponsorAccSle, *reserveSponsorAccSle);
}
// Update the existing Sponsorship object.
if (feeAmountDelta)
{
auto actualDelta = feeAmountDelta.value();
auto const currentFee = (*sponsorshipSle)[~sfFeeAmount].valueOr(XRPAmount{0});
auto const currentFee = (*sponsorship)[~sfFeeAmount].valueOr(XRPAmount{0});
// Clamp negative delta to avoid underflow.
if (actualDelta < beast::kZero && -actualDelta > currentFee)
@@ -408,11 +320,11 @@ SponsorshipSet::doApply()
newFee >= beast::kZero, "xrpl::SponsorshipSet::doApply : new fee is non-negative");
if (newFee == beast::kZero)
{
sponsorshipSle->makeFieldAbsent(sfFeeAmount);
sponsorship->makeFieldAbsent(sfFeeAmount);
}
else
{
(*sponsorshipSle)[sfFeeAmount] = newFee;
(*sponsorship)[sfFeeAmount] = newFee;
}
ctx_.view().update(sponsorAccSle);
}
@@ -421,34 +333,34 @@ SponsorshipSet::doApply()
{
if (*maxFee == beast::kZero)
{
(*sponsorshipSle).makeFieldAbsent(sfMaxFee);
(*sponsorship).makeFieldAbsent(sfMaxFee);
}
else
{
(*sponsorshipSle)[sfMaxFee] = *maxFee;
(*sponsorship)[sfMaxFee] = *maxFee;
}
}
if (remainingOwnerCountDelta)
{
std::int64_t const newCount =
totalRemainingOwnerCount(sponsorshipSle, remainingOwnerCountDelta);
sponsorship.totalRemainingOwnerCount(remainingOwnerCountDelta);
// Overflow is rejected in preclaim; underflow clamps to zero (field absent).
XRPL_ASSERT(
newCount <= static_cast<std::int64_t>(std::numeric_limits<std::uint32_t>::max()),
"xrpl::SponsorshipSet::doApply : RemainingOwnerCount does not overflow");
if (newCount <= 0)
{
sponsorshipSle->makeFieldAbsent(sfRemainingOwnerCount);
sponsorship->makeFieldAbsent(sfRemainingOwnerCount);
}
else
{
sponsorshipSle->at(sfRemainingOwnerCount) = static_cast<std::uint32_t>(newCount);
sponsorship->at(sfRemainingOwnerCount) = static_cast<std::uint32_t>(newCount);
}
}
// Apply requested flag changes.
auto flags = sponsorshipSle->getFieldU32(sfFlags);
auto flags = sponsorship->getFieldU32(sfFlags);
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
flags |= lsfSponsorshipRequireSignForFee;
@@ -461,10 +373,10 @@ SponsorshipSet::doApply()
if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForReserve))
flags &= ~lsfSponsorshipRequireSignForReserve;
if (flags != (*sponsorshipSle)[sfFlags])
(*sponsorshipSle)[sfFlags] = flags;
if (flags != (*sponsorship)[sfFlags])
(*sponsorship)[sfFlags] = flags;
view().update(sponsorshipSle);
sponsorship.update();
return tesSUCCESS;
}

View File

@@ -7,6 +7,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SponsorshipEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -68,29 +69,6 @@ decrementSponsorCount(
return tesSUCCESS;
}
// Consume the sponsor's pre-funded reserve budget and lowers the Sponsorship
// object's RemainingOwnerCount.
static TER
decrementPrefundedReserveCount(ApplyView& view, SLE::Ref sponsorshipSle, std::uint32_t const delta)
{
if (delta == 0)
return tesSUCCESS; // LCOV_EXCL_LINE
auto const currentReserveCount = sponsorshipSle->getFieldU32(sfRemainingOwnerCount);
if (currentReserveCount < delta)
{
// LCOV_EXCL_START
// Already verified by checkReserve (sufficient RemainingOwnerCount)
UNREACHABLE("xrpl::decrementPrefundedReserveCount : invalid reserve count");
return tefINTERNAL;
// LCOV_EXCL_STOP
}
sponsorshipSle->at(sfRemainingOwnerCount) = currentReserveCount - delta;
view.update(sponsorshipSle);
return tesSUCCESS;
}
std::uint32_t
SponsorshipTransfer::getFlagsMask(PreflightContext const& ctx)
{
@@ -405,12 +383,11 @@ SponsorshipTransfer::doApply()
objectSle->setAccountID(sponsorField, newSponsorID);
view().update(objectSle);
auto const sponsorshipSle = view().peek(keylet::sponsorship(newSponsorID, sponseeID));
if (sponsorshipSle)
SponsorshipEntryW sponsorship(newSponsorID, sponseeID, view(), ctx_.journal);
if (sponsorship)
{
// Update ReserveCount for sponsorship object if it exists
if (auto const ter =
decrementPrefundedReserveCount(view(), sponsorshipSle, ownerCountDelta);
if (auto const ter = sponsorship.decrementPrefundedReserveCount(ownerCountDelta);
!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}