Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions include/xrpl/ledger/ApplyView.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,50 @@ class ApplyView : public ReadView
emptyDirDelete(Keylet const& directory);
};

struct ReserveContext
{
AccountID const accountID;
SLE::pointer accountSle;
std::optional<AccountID> const sponsorID;
SLE::pointer sponsorSle;
SLE::pointer sponsorshipSle;

[[nodiscard]] bool
isSponsored() const
{
XRPL_ASSERT(
sponsorID.has_value() == !!sponsorSle,
"ReserveContext::isSponsored : sponsor existence matches sponsorSle existence");
return sponsorID.has_value();
}

[[nodiscard]] bool
hasSponsorshipObj() const
{
return !!sponsorshipSle;
}

static ReserveContext
makeFromTx(ApplyView& view, STTx const& tx);

static ReserveContext
makeFromAccount(ApplyView& view, SLE::pointer accountSle, SLE::pointer sponsorSle);

static ReserveContext
makeFromObject(ApplyView& view, SLE::ref objectSle, SLE::pointer ownerSle);
};

struct ApplyViewContext
{
ApplyView& view;
STTx const& tx;
ReserveContext reserveContext;

static ApplyViewContext
makeFromTx(ApplyView& view, STTx const& tx)
{
return {.view = view, .tx = tx, .reserveContext = ReserveContext::makeFromTx(view, tx)};
}
};

namespace directory {
Expand Down
55 changes: 5 additions & 50 deletions include/xrpl/ledger/helpers/AccountRootHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,72 +141,27 @@ ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj = 0)
void
adjustOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::int32_t accountCountAdj,
ReserveContext const& reserveCtx,
std::int32_t ownerCountAdj,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});

/** Convenience overload that accepts AccountID instead of SLE references.
*
* @param view The apply view for making changes
* @param account The account ID
* @param sponsor The optional sponsor account ID
* @param accountCountAdj Adjustment amount for the account count
* @param j Journal for logging (default: null sink)
*/
inline void
adjustOwnerCount(
ApplyView& view,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::int32_t accountCountAdj,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
adjustOwnerCount(
view,
view.peek(keylet::account(account)),
sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(),
accountCountAdj,
j);
}

/** Adjust the owner counters of the account up or down. If object has sponsor adjust its counters
* too. Used primarily just before deleting the object.
*
* @param view The apply view for making changes
* @param accountSle The account's ledger entry
* @param objectSle The object's ledger entry
* @param accountCountAdj Adjustment amount for the account count
* @param ownerCountAdj Adjustment amount for the account count
* @param j Journal for logging (default: null sink)
*/
void
adjustOwnerCountObj(
ApplyView& view,
SLE::ref accountSle,
SLE::pointer ownerSle,
SLE::ref objectSle,
std::int32_t accountCountAdj,
std::int32_t ownerCountAdj,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});

/** Convenience overload that accepts AccountID instead of account SLE reference.
*
* @param view The apply view for making changes
* @param account The account ID
* @param objectSle The object's ledger entry
* @param accountCountAdj Adjustment amount for the account count
* @param j Journal for logging (default: null sink)
*/
inline void
adjustOwnerCountObj(
ApplyView& view,
AccountID const& account,
SLE::ref objectSle,
std::int32_t accountCountAdj,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
SLE::ref accountSle = view.peek(keylet::account(account));
adjustOwnerCountObj(view, accountSle, objectSle, accountCountAdj, j);
}

/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
Expand Down
23 changes: 11 additions & 12 deletions include/xrpl/ledger/helpers/EscrowHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/Rate.h>

#include <utility>

namespace xrpl {

template <ValidIssueType T>
Expand Down Expand Up @@ -58,12 +60,10 @@ escrowUnlockApplyHelper<Issue>(
if (!view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx});
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
auto const sponsorSle = ctx.reserveContext.sponsorSle;

if (auto const ret = checkInsufficientReserve(
view, ctx.tx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
view, ctx.tx, sleDest, xrpBalance, sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
Expand Down Expand Up @@ -91,7 +91,7 @@ escrowUnlockApplyHelper<Issue>(
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
Expand Down Expand Up @@ -188,25 +188,24 @@ escrowUnlockApplyHelper<MPTIssue>(
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
auto const sponsorSle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx});
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
auto const sponsorSle = ctx.reserveContext.sponsorSle;

if (auto const ret = checkInsufficientReserve(
view, ctx.tx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
view, ctx.tx, sleDest, xrpBalance, sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
return ret;

if (auto const ter = createMPToken(view, mptID, receiver, *sponsorSle, 0);
if (auto const ter = createMPToken(view, mptID, receiver, sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}

// update owner count.
adjustOwnerCount(view, sleDest, *sponsorSle, 1, journal);
adjustOwnerCount(
view, ReserveContext::makeFromAccount(view, sleDest, sponsorSle), 1, journal);
auto mptSle = view.peek(mptKeylet);
addSponsorToLedgerEntry(mptSle, *sponsorSle);
addSponsorToLedgerEntry(mptSle, sponsorSle);
}

if (!view.exists(mptKeylet) && !receiverIssuer)
Expand Down
4 changes: 2 additions & 2 deletions include/xrpl/ledger/helpers/MPTokenHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,10 @@ createMPToken(

TER
checkCreateMPT(
xrpl::ApplyView& view,
ApplyView& view,
ReserveContext const& reserveCtx,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
beast::Journal j);

//------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion include/xrpl/ledger/helpers/NFTokenHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftoken

/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft);
insertToken(ApplyViewContext& view, AccountID owner, STObject&& nft);

/** Remove the token from the owner's token directory. */
TER
Expand Down
16 changes: 0 additions & 16 deletions include/xrpl/ledger/helpers/SponsorHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,6 @@ getTxReserveSponsorAccountID(STTx const& tx)
return {};
}

inline std::expected<SLE::pointer, TER>
getTxReserveSponsor(ApplyViewContext const& ctx)
{
auto const sponsorID = getTxReserveSponsorAccountID(ctx.tx);
if (sponsorID)
{
auto sle = ctx.view.peek(keylet::account(*sponsorID));

// already checked in Transactor::checkSponsor
if (!sle)
return std::unexpected(tecINTERNAL);
return sle;
}
return SLE::pointer();
}

inline std::expected<SLE::const_pointer, TER>
getTxReserveSponsor(ReadView const& view, STTx const& tx)
{
Expand Down
2 changes: 1 addition & 1 deletion include/xrpl/tx/ApplyContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class ApplyContext
getApplyViewContext()
{
XRPL_ASSERT(view_.has_value(), "Previous view exists");
return {.view = *view_, .tx = tx};
return ApplyViewContext::makeFromTx(*view_, tx);
}

private:
Expand Down
55 changes: 55 additions & 0 deletions src/libxrpl/ledger/ApplyView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STVector256.h>

#include <algorithm>
Expand Down Expand Up @@ -415,4 +418,56 @@
return true;
}

ReserveContext
ReserveContext::makeFromTx(ApplyView& view, STTx const& tx)
{
auto const account = tx[sfAccount];
std::optional<AccountID> const sponsor =
isReserveSponsored(tx) ? std::optional<AccountID>{tx[sfSponsor]} : std::nullopt;

return {
.accountID = account,
.accountSle = view.peek(keylet::account(account)),
.sponsorID = sponsor,
.sponsorSle = sponsor ? view.peek(keylet::account(*sponsor)) : nullptr,
.sponsorshipSle = sponsor ? view.peek(keylet::sponsorship(*sponsor, account)) : nullptr,
};
}

ReserveContext
ReserveContext::makeFromAccount(ApplyView& view, SLE::pointer accountSle, SLE::pointer sponsorSle)
{
if (!accountSle)
Throw<std::runtime_error>("ReserveContext::makeFromAccount : valid account sle");

Check warning on line 441 in src/libxrpl/ledger/ApplyView.cpp

View check run for this annotation

Codecov / codecov/patch

src/libxrpl/ledger/ApplyView.cpp#L441

Added line #L441 was not covered by tests

auto const accountID = accountSle->getAccountID(sfAccount);
std::optional<AccountID> const sponsorID =
sponsorSle ? std::optional<AccountID>{sponsorSle->getAccountID(sfAccount)} : std::nullopt;
return {
.accountID = accountID,
.accountSle = accountSle,
.sponsorID = sponsorID,
.sponsorSle = sponsorSle,
.sponsorshipSle =
sponsorID ? view.peek(keylet::sponsorship(*sponsorID, accountID)) : nullptr,
};
}

ReserveContext
ReserveContext::makeFromObject(ApplyView& view, SLE::ref objectSle, SLE::pointer ownerSle)
{
auto const accountID = ownerSle->getAccountID(sfAccount);
Comment on lines +458 to +459

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: LOW

makeFromObject dereferences ownerSle without a null check, unlike makeFromAccount which explicitly throws on null. Multiple callers pass view.peek() results (e.g., OfferHelpers.cpp, CredentialHelpers.cpp) that can return null, risking a null-pointer dereference crash.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Add a null check for ownerSle before dereferencing it at line 440, consistent with the sibling makeFromAccount function which throws on null at line 422. Add if (!ownerSle) Throw<std::runtime_error>("ReserveContext::makeFromObject : valid owner sle"); before the dereference.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
{
auto const accountID = ownerSle->getAccountID(sfAccount);
{
if (!ownerSle)
Throw<std::runtime_error>("ReserveContext::makeFromObject : valid owner sle");
auto const accountID = ownerSle->getAccountID(sfAccount);

SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle);
std::optional<AccountID> const sponsorID =
sponsorSle ? std::optional<AccountID>{sponsorSle->getAccountID(sfAccount)} : std::nullopt;
return {
.accountID = accountID,
.accountSle = ownerSle,
.sponsorID = sponsorID,
.sponsorSle = sponsorSle,
.sponsorshipSle =
sponsorID ? view.peek(keylet::sponsorship(*sponsorID, accountID)) : nullptr,
};
}

} // namespace xrpl
14 changes: 7 additions & 7 deletions src/libxrpl/ledger/View.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
Expand Down Expand Up @@ -444,7 +443,11 @@ doWithdraw(
if (dstAcct == senderAcct)
{
if (auto const ter = addEmptyHolding(
{.view = ctx.view, .tx = ctx.tx}, senderAcct, priorBalance, amount.asset(), j);
ApplyViewContext::makeFromTx(ctx.view, ctx.tx),
senderAcct,
priorBalance,
amount.asset(),
j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
Expand All @@ -470,14 +473,11 @@ doWithdraw(
// LCOV_EXCL_STOP
}

auto const sponsorSle = getTxReserveSponsor({.view = ctx.view, .tx = ctx.tx});
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
auto const sponsorSle = ctx.reserveContext.sponsorSle;

// Move the funds directly from the broker's pseudo-account to the
// dstAcct
return accountSend(
ctx.view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes);
return accountSend(ctx.view, sourceAcct, dstAcct, amount, j, sponsorSle, WaiveTransferFee::Yes);
}

TER
Expand Down
Loading
Loading