add create/destroy helpers

This commit is contained in:
Mayukha Vadari
2026-07-10 16:43:52 -04:00
parent 9d11ba0ca7
commit b8b9da92ee
33 changed files with 469 additions and 782 deletions

View File

@@ -48,11 +48,9 @@ namespace xrpl {
*/
[[nodiscard]] TER
canApplyToBrokerCover(
ReadView const& view,
LoanBrokerEntry<ReadView> const& sleBroker,
Asset const& vaultAsset,
STAmount const& amount,
beast::Journal j,
std::string_view logPrefix);
// Lending protocol has dependencies, so capture them here.
@@ -548,11 +546,9 @@ enum class LoanPaymentType { Regular = 0, Late, Full, Overpayment };
std::expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
LoanEntry<ApplyView>& loan,
LoanBrokerEntry<ReadView> const& brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
beast::Journal j);
LoanPaymentType const paymentType);
} // namespace xrpl

View File

@@ -3,12 +3,22 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h> // adjustOwnerCount
#include <xrpl/ledger/helpers/DirectoryHelpers.h> // describeOwnerDir
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h> // keylet::account, keylet::ownerDir
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <concepts>
#include <cstdint>
#include <memory>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <vector>
namespace xrpl {
@@ -16,6 +26,23 @@ namespace xrpl {
template <typename V>
concept WritableView = std::derived_from<V, ApplyView>;
/** Describes one directory a ledger entry is linked into, for create()/destroy().
*
* @param owner the account whose owner directory this is (the directory
* is keylet::ownerDir(owner)).
* @param node the field on the entry holding this directory's page
* index (sfOwnerNode, sfDestinationNode, sfSubjectNode, ...).
* @param countsToward whether linking here consumes `owner`'s OwnerCount /
* reserve (true for the owning account, false for auxiliary
* links such as a destination's tracking directory).
*/
struct OwnerDirLink
{
AccountID owner;
SField const* node;
bool countsToward;
};
/**
* View-parameterized base class for all ledger entry wrappers.
*
@@ -171,6 +198,126 @@ public:
sle_ = std::make_shared<SLE>(key_);
}
/** The field holding the account that owns this entry (sfAccount, sfOwner,
* sfIssuer, ...).
*
* Single-directory entry types override this to name their owning-account
* field; the default ownerDirs() then links a single owner directory keyed
* on it. Types that live in multiple directories override ownerDirs()
* directly instead. The generic base has no owner.
*/
[[nodiscard]] virtual SField const&
ownerField() const
{
UNREACHABLE("xrpl::SLEBase::ownerField : type does not define an owner field");
return sfAccount; // unreachable; present only to satisfy the return type
}
/** The directories this entry is linked into, and where each stores its
* page index.
*
* Default: a single owner directory for ownerField()'s account, recorded in
* sfOwnerNode, counting toward that account's reserve. Types linked into
* more than one directory (e.g. Check/PayChannel/Escrow's destination
* directory, Credential's subject directory) override this to list them;
* only links with `countsToward == true` consume an OwnerCount/reserve slot.
*/
[[nodiscard]] virtual std::vector<OwnerDirLink>
ownerDirs() const
{
return {{sle_->getAccountID(ownerField()), &sfOwnerNode, /*countsToward=*/true}};
}
/** Number of OwnerCount/reserve slots a counted link consumes (default 1).
* Types whose footprint scales with their contents (e.g. Oracle) override.
*/
[[nodiscard]] virtual std::uint32_t
reserveCount() const
{
return 1;
}
/** Link a freshly-populated entry into its owner directories and insert it.
*
* Handles the create-time boilerplate shared by owned ledger entries:
* 1. reserve check against `ownerReserveBalance` (the owner's pre-fee XRP
* balance — pass the transactor's preFeeBalance_ when the entry is
* owned by the transaction submitter). Pass std::nullopt to skip the
* check entirely, e.g. for entries an internal caller installs on a
* pseudo-account (VaultCreate),
* 2. link into each ownerDirs() directory, recording the page in its node
* field,
* 3. bump the OwnerCount of each counted owner by reserveCount(),
* 4. insert the entry into the view.
*
* The caller must have already called newSLE() and populated the entry's
* domain fields (in particular the account fields ownerDirs() reads).
*/
[[nodiscard]] TER
create(std::optional<XRPAmount> ownerReserveBalance)
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::create : can modify");
auto const dirs = ownerDirs();
for (auto const& d : dirs)
{
if (!d.countsToward)
continue;
auto const ownerSle = view_.peek(keylet::account(d.owner));
if (!ownerSle)
return tecNO_ENTRY; // LCOV_EXCL_LINE
if (ownerReserveBalance &&
*ownerReserveBalance <
view_.fees().accountReserve((*ownerSle)[sfOwnerCount] + reserveCount()))
return tecINSUFFICIENT_RESERVE;
}
for (auto const& d : dirs)
{
auto const page =
view_.dirInsert(keylet::ownerDir(d.owner), key_.key, describeOwnerDir(d.owner));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sle_->setFieldU64(*d.node, *page);
if (d.countsToward)
adjustOwnerCount(view_, view_.peek(keylet::account(d.owner)), reserveCount(), j_);
}
view_.insert(sle_);
return tesSUCCESS;
}
/** Unlink an owned entry from its directories and erase it.
*
* Inverse of create(): removes the entry from each ownerDirs() directory
* (using the stored node fields), decrements each counted owner's OwnerCount
* by reserveCount(), and erases the entry.
*/
[[nodiscard]] TER
destroy()
requires kIsWritable
{
XRPL_ASSERT(canModify(), "xrpl::SLEBase::destroy : can modify");
for (auto const& d : ownerDirs())
{
if (!view_.dirRemove(
keylet::ownerDir(d.owner),
sle_->getFieldU64(*d.node),
key_.key,
/*keepRoot=*/false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
if (d.countsToward)
if (auto ownerSle = view_.peek(keylet::account(d.owner)))
adjustOwnerCount(
view_, ownerSle, -static_cast<std::int32_t>(reserveCount()), j_);
}
view_.erase(sle_);
return tesSUCCESS;
}
[[nodiscard]] beast::Journal
journal() const
{

View File

@@ -56,6 +56,19 @@ public:
: SLEBase<ViewT>(keylet::check(id, seq), view, j)
{
}
// Owner dir (counts toward the source's reserve) + destination tracking dir
// (added only for a real, non-self check, matching CheckCreate).
[[nodiscard]] std::vector<OwnerDirLink>
ownerDirs() const override
{
auto const owner = this->sle()->getAccountID(sfAccount);
auto const dest = this->sle()->getAccountID(sfDestination);
std::vector<OwnerDirLink> dirs{{owner, &sfOwnerNode, /*countsToward=*/true}};
if (dest != owner)
dirs.push_back({dest, &sfDestinationNode, /*countsToward=*/false});
return dirs;
}
};
template <typename ViewT>
@@ -73,6 +86,12 @@ public:
: SLEBase<ViewT>(keylet::did(account), view, j)
{
}
[[nodiscard]] SField const&
ownerField() const override
{
return sfAccount;
}
};
template <typename ViewT>
@@ -142,6 +161,12 @@ public:
: SLEBase<ViewT>(keylet::ticket(id, ticketSeq), view, j)
{
}
[[nodiscard]] SField const&
ownerField() const override
{
return sfAccount;
}
};
template <typename ViewT>
@@ -262,6 +287,12 @@ public:
: SLEBase<ViewT>(keylet::depositPreauth(owner, preauthorized), view, j)
{
}
[[nodiscard]] SField const&
ownerField() const override
{
return sfAccount;
}
};
template <typename ViewT>
@@ -351,6 +382,29 @@ public:
: SLEBase<ViewT>(keylet::escrow(src, seq), view, j)
{
}
// Owner dir (counts toward the sender's reserve) plus tracking dirs: the
// destination's (when not a self-send) and, for IOU escrows, the issuer's
// (to track the locked balance). MPT escrows track the lock on the issuance
// object instead, so they take no issuer dir. Mirrors EscrowCreate.
[[nodiscard]] std::vector<OwnerDirLink>
ownerDirs() const override
{
auto const& sle = *this->sle();
AccountID const account = sle.getAccountID(sfAccount);
AccountID const dest = sle.getAccountID(sfDestination);
STAmount const amount = sle.getFieldAmount(sfAmount);
std::vector<OwnerDirLink> dirs;
dirs.push_back({account, &sfOwnerNode, /*countsToward=*/true});
if (dest != account)
dirs.push_back({dest, &sfDestinationNode, /*countsToward=*/false});
AccountID const issuer = amount.getIssuer();
if (!isXRP(amount) && issuer != account && issuer != dest && !amount.holds<MPTIssue>())
dirs.push_back({issuer, &sfIssuerNode, /*countsToward=*/false});
return dirs;
}
};
template <typename ViewT>
@@ -370,6 +424,16 @@ public:
: SLEBase<ViewT>(keylet::payChannel(src, dst, seq), view, j)
{
}
// Owner dir (counts toward the source's reserve) + destination tracking dir
// (PaymentChannelCreate forbids dst == src, so both are always present).
[[nodiscard]] std::vector<OwnerDirLink>
ownerDirs() const override
{
return {
{this->sle()->getAccountID(sfAccount), &sfOwnerNode, /*countsToward=*/true},
{this->sle()->getAccountID(sfDestination), &sfDestinationNode, /*countsToward=*/false}};
}
};
template <typename ViewT>
@@ -406,6 +470,12 @@ public:
: SLEBase<ViewT>(keylet::mptokenIssuance(seq, issuer), view, j)
{
}
[[nodiscard]] SField const&
ownerField() const override
{
return sfIssuer;
}
};
template <typename ViewT>
@@ -442,6 +512,19 @@ public:
: SLEBase<ViewT>(keylet::oracle(account, documentID), view, j)
{
}
[[nodiscard]] SField const&
ownerField() const override
{
return sfOwner;
}
// An Oracle with more than five price-data pairs occupies two reserve slots.
[[nodiscard]] std::uint32_t
reserveCount() const override
{
return this->sle()->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
}
};
template <typename ViewT>
@@ -461,6 +544,26 @@ public:
: SLEBase<ViewT>(keylet::credential(subject, issuer, credType), view, j)
{
}
// A credential lives in both the issuer's and subject's directories, but is
// only counted against one owner's reserve at a time: the issuer holds it
// until the subject accepts (lsfAccepted), after which the subject owns it.
// A self-issued credential is always owned (and counted) by the issuer.
// Mirrors CredentialCreate / credentials::deleteSLE.
[[nodiscard]] std::vector<OwnerDirLink>
ownerDirs() const override
{
auto const& sle = *this->sle();
AccountID const issuer = sle.getAccountID(sfIssuer);
AccountID const subject = sle.getAccountID(sfSubject);
bool const accepted = sle.isFlag(lsfAccepted);
std::vector<OwnerDirLink> dirs;
dirs.push_back({issuer, &sfIssuerNode, /*countsToward=*/!accepted || subject == issuer});
if (subject != issuer)
dirs.push_back({subject, &sfSubjectNode, /*countsToward=*/accepted});
return dirs;
}
};
template <typename ViewT>
@@ -479,6 +582,12 @@ public:
: SLEBase<ViewT>(keylet::permissionedDomain(account, seq), view, j)
{
}
[[nodiscard]] SField const&
ownerField() const override
{
return sfOwner;
}
};
template <typename ViewT>
@@ -497,6 +606,16 @@ public:
: SLEBase<ViewT>(keylet::delegate(account, authorizedAccount), view, j)
{
}
// Owner dir (counts toward the delegator's reserve) + the authorized
// account's dir (so AccountDelete can find inbound delegations; no count).
[[nodiscard]] std::vector<OwnerDirLink>
ownerDirs() const override
{
return {
{this->sle()->getAccountID(sfAccount), &sfOwnerNode, /*countsToward=*/true},
{this->sle()->getAccountID(sfAuthorize), &sfDestinationNode, /*countsToward=*/false}};
}
};
template <typename ViewT>

View File

@@ -41,32 +41,26 @@ public:
*/
static TER
defaultLoan(
ApplyView& view,
LoanEntry<ApplyView>& loanSle,
LoanBrokerEntry<ApplyView>& brokerSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset,
beast::Journal j);
Asset const& vaultAsset);
/** Helper function that might be needed by other transactors
*/
static TER
impairLoan(
ApplyView& view,
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset,
beast::Journal j);
Asset const& vaultAsset);
/** Helper function that might be needed by other transactors
*/
[[nodiscard]] static TER
unimpairLoan(
ApplyView& view,
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset,
beast::Journal j);
Asset const& vaultAsset);
TER
doApply() override;

View File

@@ -76,52 +76,26 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j)
if (!sleCredential)
return tecNO_ENTRY;
auto delSLE = [&view, &sleCredential, j](
AccountID const& account, SField const& node, bool isOwner) -> TER {
AccountRootEntry<ApplyView> sleAccount{keylet::account(account), view};
if (!sleAccount)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
return tecINTERNAL;
// LCOV_EXCL_STOP
}
// Remove object from owner directory
std::uint64_t const page = sleCredential->getFieldU64(node);
if (!view.dirRemove(keylet::ownerDir(account), page, sleCredential->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Credential from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
if (isOwner)
adjustOwnerCount(view, sleAccount.mutableSle(), -1, j);
return tesSUCCESS;
};
// Historically deleteSLE fetched both the issuer's and (for a third-party
// credential) the subject's account and failed if either was missing, even
// though only one of them is counted against a reserve. Preserve that
// stricter contract: a corrupted view missing one of these accounts must
// report tecINTERNAL rather than silently unlinking.
auto const issuer = sleCredential->getAccountID(sfIssuer);
auto const subject = sleCredential->getAccountID(sfSubject);
bool const accepted = sleCredential->isFlag(lsfAccepted);
auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
if (!isTesSuccess(err))
return err;
if (subject != issuer)
if (!view.exists(keylet::account(issuer)) ||
(subject != issuer && !view.exists(keylet::account(subject))))
{
err = delSLE(subject, sfSubjectNode, accepted);
if (!isTesSuccess(err))
return err;
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
return tecINTERNAL;
}
// Remove object from ledger
view.erase(sleCredential);
return tesSUCCESS;
// Unlink the credential from the issuer's and subject's directories,
// decrementing whichever account currently owns it (the issuer until the
// subject accepts, the subject afterwards), and erase it. See
// CredentialEntry::ownerDirs().
CredentialEntry<ApplyView> cred{sleCredential, view, j};
return cred.destroy();
}
NotTEC

View File

@@ -32,13 +32,14 @@ namespace xrpl {
[[nodiscard]] TER
canApplyToBrokerCover(
ReadView const& view,
LoanBrokerEntry<ReadView> const& sleBroker,
Asset const& vaultAsset,
STAmount const& amount,
beast::Journal j,
std::string_view logPrefix)
{
ReadView const& view = sleBroker.readView();
beast::Journal const j = sleBroker.journal();
XRPL_ASSERT(
sleBroker && sleBroker->getType() == ltLOAN_BROKER,
"xrpl::canApplyToBrokerCover : valid LoanBroker sle");
@@ -1783,15 +1784,16 @@ computeLoanProperties(
std::expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
LoanEntry<ApplyView>& loan,
LoanBrokerEntry<ReadView> const& brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
beast::Journal j)
LoanPaymentType const paymentType)
{
using namespace Lending;
ApplyView& view = loan.applyView();
beast::Journal const j = loan.journal();
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);

View File

@@ -29,32 +29,8 @@ closeChannel(
beast::Journal j)
{
AccountID const src = (*slep)[sfAccount];
// Remove PayChan from owner directory
{
auto const page = (*slep)[sfOwnerNode];
if (!view.dirRemove(keylet::ownerDir(src), page, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from src owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Remove PayChan from recipient's owner directory, if present.
if (auto const page = (*slep)[~sfDestinationNode])
{
auto const dst = (*slep)[sfDestination];
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from dst owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Transfer amount back to owner, decrement owner count
// Transfer any remaining balance back to the owner.
AccountRootEntry<ApplyView> sle{src, view};
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -62,12 +38,11 @@ closeChannel(
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
adjustOwnerCount(view, sle.mutableSle(), -1, j);
sle.update();
// Remove PayChan from ledger
slep.erase();
return tesSUCCESS;
// Unlink the channel from the owner and destination directories, decrement
// the owner's OwnerCount, and erase it. See PayChannelEntry::ownerDirs().
return slep.destroy();
}
uint32_t

View File

@@ -63,41 +63,9 @@ CheckCancel::doApply()
return tecNO_ENTRY;
}
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
AccountID const dstId{sleCheck->getAccountID(sfDestination)};
auto viewJ = ctx_.registry.get().getJournal("View");
// If the check is not written to self (and it shouldn't be), remove the
// check from the destination account root.
if (srcId != dstId)
{
std::uint64_t const page{(*sleCheck)[sfDestinationNode]};
if (!view().dirRemove(keylet::ownerDir(dstId), page, sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from destination.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
{
std::uint64_t const page{(*sleCheck)[sfOwnerNode]};
if (!view().dirRemove(keylet::ownerDir(srcId), page, sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// If we succeeded, update the check owner's reserve.
AccountRootEntry<ApplyView> sleSrc{keylet::account(srcId), view()};
adjustOwnerCount(view(), sleSrc.mutableSle(), -1, viewJ);
// Remove check from ledger.
sleCheck.erase();
return tesSUCCESS;
// Unlink the check from the source (and destination) directories, decrement
// the source's OwnerCount, and erase it. See CheckEntry::ownerDirs().
return sleCheck.destroy();
}
void

View File

@@ -560,33 +560,11 @@ CheckCash::doApply()
}
}
// Check was cashed. If not a self send (and it shouldn't be), remove
// check link from destination directory.
if (srcId != accountID_ &&
!psb.dirRemove(
keylet::ownerDir(accountID_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from destination.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// Remove check from check owner's directory.
if (!psb.dirRemove(keylet::ownerDir(srcId), sleCheck->at(sfOwnerNode), sleCheck->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// If we succeeded, update the check owner's reserve.
AccountRootEntry<ApplyView> sleSrcAccount{keylet::account(srcId), psb};
adjustOwnerCount(psb, sleSrcAccount.mutableSle(), -1, viewJ);
// Remove check from ledger.
psb.erase(sleCheck);
// Check was cashed. Unlink it from the owner (and destination) directories,
// decrement the source's OwnerCount, and erase it. See CheckEntry::ownerDirs().
CheckEntry<ApplyView> checkEntry{sleCheck, psb};
if (auto const ter = checkEntry.destroy(); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
psb.apply(ctx_.rawView());
return tesSUCCESS;

View File

@@ -192,16 +192,6 @@ CheckCreate::doApply()
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// A check counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
{
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
// Note that we use the value from the sequence or ticket as the
// Check sequence. For more explanation see comments in SeqProxy.h.
std::uint32_t const seq = ctx_.tx.getSeqValue();
@@ -223,40 +213,10 @@ CheckCreate::doApply()
if (auto const expiry = ctx_.tx[~sfExpiration])
sleCheck->setFieldU32(sfExpiration, *expiry);
sleCheck.insert();
auto viewJ = ctx_.registry.get().getJournal("View");
// If it's not a self-send (and it shouldn't be), add Check to the
// destination's owner directory.
if (dstAccountId != accountID_)
{
auto const page = view().dirInsert(
keylet::ownerDir(dstAccountId), checkKeylet, describeOwnerDir(dstAccountId));
JLOG(j_.trace()) << "Adding Check to destination directory " << to_string(checkKeylet.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sleCheck->setFieldU64(sfDestinationNode, *page);
}
{
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), checkKeylet, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding Check to owner directory " << to_string(checkKeylet.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sleCheck->setFieldU64(sfOwnerNode, *page);
}
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sle.mutableSle(), 1, viewJ);
return tesSUCCESS;
// Reserve check (source's pre-fee balance) + link into the source owner
// directory and the destination tracking directory + bump the source's
// OwnerCount + insert. See CheckEntry::ownerDirs().
return sleCheck.create(preFeeBalance_);
}
void

View File

@@ -128,17 +128,6 @@ CredentialCreate::doApply()
sleCred->setFieldU32(sfExpiration, *optExp);
}
AccountRootEntry<ApplyView> sleIssuer{keylet::account(accountID_), view()};
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
{
STAmount const reserve{
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
sleCred->setAccountID(sfSubject, subject);
sleCred->setAccountID(sfIssuer, accountID_);
sleCred->setFieldVL(sfCredentialType, credType);
@@ -146,38 +135,15 @@ CredentialCreate::doApply()
if (ctx_.tx.isFieldPresent(sfURI))
sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
{
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), credentialKey, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleCred->setFieldU64(sfIssuerNode, *page);
adjustOwnerCount(view(), sleIssuer.mutableSle(), 1, j_);
}
// A self-issued credential is accepted immediately.
if (subject == accountID_)
{
sleCred->setFieldU32(sfFlags, lsfAccepted);
}
else
{
// Added to both dirs, owned only by issuer. CredentialAccept will transfer ownership to
// subject. CredentialDelete will remove from both dirs and decrement 1 ownerCount.
auto const page =
view().dirInsert(keylet::ownerDir(subject), credentialKey, describeOwnerDir(subject));
JLOG(j_.trace()) << "Adding Credential to subject directory "
<< to_string(credentialKey.key) << ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleCred->setFieldU64(sfSubjectNode, *page);
}
sleCred.insert();
return tesSUCCESS;
// Reserve check (issuer's pre-fee balance) + link into the issuer's owner
// directory (counted) and, for a third-party subject, the subject's tracking
// directory + bump the issuer's OwnerCount + insert. Ownership transfers to
// the subject on CredentialAccept. See CredentialEntry::ownerDirs().
return sleCred.create(preFeeBalance_);
}
void

View File

@@ -99,41 +99,15 @@ DelegateSet::doApply()
if (permissions.empty())
return tecINTERNAL; // LCOV_EXCL_LINE
STAmount const reserve{
ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
sle.newSLE();
sle->setAccountID(sfAccount, accountID_);
sle->setAccountID(sfAuthorize, authAccount);
sle->setFieldArray(sfPermissions, permissions);
// Add to delegating account's owner directory
auto const page = ctx_.view().dirInsert(
keylet::ownerDir(accountID_), delegateKey, describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
// Add to authorized account's owner directory so AccountDelete can find
// and clean up inbound delegations when the authorized account is deleted.
auto const destPage = ctx_.view().dirInsert(
keylet::ownerDir(authAccount), delegateKey, describeOwnerDir(authAccount));
if (!destPage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfDestinationNode] = *destPage;
sle.insert();
adjustOwnerCount(ctx_.view(), sleOwner.mutableSle(), 1, ctx_.journal);
return tesSUCCESS;
// Reserve check (delegator's pre-fee balance) + link into the delegator's
// owner directory and the authorized account's directory + bump the
// delegator's OwnerCount + insert. See DelegateEntry::ownerDirs().
return sle.create(preFeeBalance_);
}
TER
@@ -142,40 +116,11 @@ DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j)
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const delegator = (*sle)[sfAccount];
auto const delegatee = (*sle)[sfAuthorize];
// Remove from delegating account's owner directory
if (!view.dirRemove(keylet::ownerDir(delegator), (*sle)[sfOwnerNode], sle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Delegate from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// Remove from authorized account's owner directory, if present
if (auto const optPage = (*sle)[~sfDestinationNode])
{
if (!view.dirRemove(keylet::ownerDir(delegatee), *optPage, sle->key(), false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Delegate from authorized account.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Only the delegating account's owner count was incremented on creation
AccountRootEntry<ApplyView> sleOwner{keylet::account(delegator), view};
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleOwner.mutableSle(), -1, j);
view.erase(sle);
return tesSUCCESS;
// Unlink from the delegator's owner directory and the authorized account's
// directory, decrement the delegator's OwnerCount, and erase. Only the
// delegator's count was bumped on creation. See DelegateEntry::ownerDirs().
DelegateEntry<ApplyView> delegate{sle, view, j};
return delegate.destroy();
}
void

View File

@@ -38,25 +38,8 @@ DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner)
TER
DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, beast::Journal j)
{
// Remove object from owner directory
if (!view.dirRemove(keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete DID from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
AccountRootEntry<ApplyView> sleOwner{keylet::account(owner), view};
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleOwner.mutableSle(), -1, j);
sleOwner.update();
// Remove object from ledger
view.erase(sle);
return tesSUCCESS;
DIDEntry<ApplyView> did{sle, view, j};
return did.destroy();
}
TER

View File

@@ -63,39 +63,6 @@ DIDSet::preflight(PreflightContext const& ctx)
return tesSUCCESS;
}
static TER
addSLE(ApplyContext& ctx, DIDEntry<ApplyView>& sle, AccountID const& owner)
{
AccountRootEntry<ApplyView> sleAccount{keylet::account(owner), ctx.view()};
if (!sleAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
// Check reserve availability for new object creation
{
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
auto const reserve = ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}
// Add ledger object to ledger
sle.insert();
// Add ledger object to owner's page
{
auto page =
ctx.view().dirInsert(keylet::ownerDir(owner), sle->key(), describeOwnerDir(owner));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
}
adjustOwnerCount(ctx.view(), sleAccount.mutableSle(), 1, ctx.journal);
sleAccount.update();
return tesSUCCESS;
}
TER
DIDSet::doApply()
{
@@ -148,7 +115,7 @@ DIDSet::doApply()
return tecEMPTY_DID;
}
return addSLE(ctx_, sleDID, accountID_);
return sleDID.create(preFeeBalance_);
}
void

View File

@@ -140,30 +140,6 @@ EscrowCancel::doApply()
AccountID const account = (*slep)[sfAccount];
// Remove escrow from owner directory
{
auto const page = (*slep)[sfOwnerNode];
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Remove escrow from recipient's owner directory, if present.
if (auto const optPage = (*slep)[~sfDestinationNode]; optPage)
{
if (!ctx_.view().dirRemove(keylet::ownerDir((*slep)[sfDestination]), *optPage, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
AccountRootEntry<ApplyView> sle{keylet::account(account), ctx_.view()};
STAmount const amount = slep->getFieldAmount(sfAmount);
@@ -197,27 +173,14 @@ EscrowCancel::doApply()
amount.asset().value());
!isTesSuccess(ret))
return ret; // LCOV_EXCL_LINE
// Remove escrow from issuers owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
{
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
}
adjustOwnerCount(ctx_.view(), sle.mutableSle(), -1, ctx_.journal);
sle.update();
// Remove escrow from ledger
slep.erase();
return tesSUCCESS;
// Unlink the escrow from the sender's owner directory (and the destination
// and issuer tracking directories, if present), decrement the sender's
// OwnerCount, and erase it. See EscrowEntry::ownerDirs().
return slep.destroy();
}
void

View File

@@ -484,45 +484,12 @@ EscrowCreate::doApply()
(*slep)[sfTransferRate] = xferRate.value;
}
slep.insert();
// Add escrow to sender's owner directory
{
auto page = ctx_.view().dirInsert(
keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfOwnerNode] = *page;
}
// If it's not a self-send, add escrow to recipient's owner directory.
AccountID const dest = ctx_.tx[sfDestination];
if (dest != accountID_)
{
auto page =
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfDestinationNode] = *page;
}
// IOU escrow objects are added to the issuer's owner directory to help
// track the total locked balance. For MPT, this isn't necessary because the
// locked balance is already stored directly in the MPTokenIssuance object.
// Deduct/lock the escrowed amount from the sender.
AccountID const issuer = amount.getIssuer();
if (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds<MPTIssue>())
{
auto page =
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfIssuerNode] = *page;
}
// Deduct owner's balance
if (isXRP(amount))
{
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
sle.update();
}
else
{
@@ -537,10 +504,11 @@ EscrowCreate::doApply()
}
}
// increment owner count
adjustOwnerCount(ctx_.view(), sle.mutableSle(), 1, ctx_.journal);
sle.update();
return tesSUCCESS;
// Link the escrow into the sender's owner directory (counts toward reserve),
// plus the destination and (for IOU) issuer tracking directories where
// applicable, bump the sender's OwnerCount, and insert. The recipient/issuer
// tracking dirs help track locked balances. See EscrowEntry::ownerDirs().
return slep.create(preFeeBalance_);
}
void

View File

@@ -317,30 +317,6 @@ EscrowFinish::doApply()
AccountID const account = (*slep)[sfAccount];
// Remove escrow from owner directory
{
auto const page = (*slep)[sfOwnerNode];
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
// Remove escrow from recipient's owner directory, if present.
if (auto const optPage = (*slep)[~sfDestinationNode])
{
if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
STAmount const amount = slep->getFieldAmount(sfAmount);
// Transfer amount to destination
if (isXRP(amount))
@@ -374,30 +350,14 @@ EscrowFinish::doApply()
amount.asset().value());
!isTesSuccess(ret))
return ret;
// Remove escrow from issuers owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
{
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
}
sled.update();
// Adjust source owner count
AccountRootEntry<ApplyView> sle{keylet::account(account), ctx_.view()};
adjustOwnerCount(ctx_.view(), sle.mutableSle(), -1, ctx_.journal);
sle.update();
// Remove escrow from ledger
slep.erase();
return tesSUCCESS;
// Unlink the escrow from the sender's owner directory (and the destination
// and issuer tracking directories, if present), decrement the sender's
// OwnerCount, and erase it. See EscrowEntry::ownerDirs().
return slep.destroy();
}
void

View File

@@ -303,11 +303,9 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
STAmount const& clawAmount = *findClawAmount;
if (auto const ret = canApplyToBrokerCover(
ctx.view,
LoanBrokerEntry<ReadView>{sleBroker, ctx.view},
LoanBrokerEntry<ReadView>{sleBroker, ctx.view, ctx.j},
vaultAsset,
clawAmount,
ctx.j,
"LoanBrokerCoverClawback"))
return ret;

View File

@@ -96,11 +96,9 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
// Helper handles both IOU and MPT correctly without explicit branching.
if (auto const ret = canApplyToBrokerCover(
ctx.view,
LoanBrokerEntry<ReadView>{sleBroker, ctx.view},
LoanBrokerEntry<ReadView>{sleBroker, ctx.view, ctx.j},
vaultAsset,
amount,
ctx.j,
"LoanBrokerCoverWithdraw"))
return ret;

View File

@@ -146,13 +146,14 @@ owedToVault(LoanEntry<ApplyView> const& loanSle)
TER
LoanManage::defaultLoan(
ApplyView& view,
LoanEntry<ApplyView>& loanSle,
LoanBrokerEntry<ApplyView>& brokerSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset,
beast::Journal j)
Asset const& vaultAsset)
{
ApplyView& view = loanSle.applyView();
beast::Journal const j = loanSle.journal();
// Calculate the amount of the Default that First-Loss Capital covers:
std::int32_t const loanScale = loanSle->at(sfLoanScale);
@@ -297,12 +298,13 @@ LoanManage::defaultLoan(
TER
LoanManage::impairLoan(
ApplyView& view,
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset,
beast::Journal j)
Asset const& vaultAsset)
{
ApplyView& view = loanSle.applyView();
beast::Journal const j = loanSle.journal();
Number const lossUnrealized = owedToVault(loanSle);
// The vault may be at a different scale than the loan. Reduce rounding
@@ -339,12 +341,13 @@ LoanManage::impairLoan(
[[nodiscard]] TER
LoanManage::unimpairLoan(
ApplyView& view,
LoanEntry<ApplyView>& loanSle,
VaultEntry<ApplyView>& vaultSle,
Asset const& vaultAsset,
beast::Journal j)
Asset const& vaultAsset)
{
ApplyView& view = loanSle.applyView();
beast::Journal const j = loanSle.journal();
// The vault may be at a different scale than the loan. Reduce rounding
// errors during the accounting by rounding some of the values to that
// scale.
@@ -393,16 +396,16 @@ LoanManage::doApply()
auto& view = ctx_.view();
auto const loanID = tx[sfLoanID];
LoanEntry<ApplyView> loanSle{keylet::loan(loanID), view};
LoanEntry<ApplyView> loanSle{keylet::loan(loanID), view, j_};
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerID = loanSle->at(sfLoanBrokerID);
LoanBrokerEntry<ApplyView> brokerSle{keylet::loanBroker(brokerID), view};
LoanBrokerEntry<ApplyView> brokerSle{keylet::loanBroker(brokerID), view, j_};
if (!brokerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
VaultEntry<ApplyView> vaultSle{keylet::vault(brokerSle->at(sfVaultID)), view};
VaultEntry<ApplyView> vaultSle{keylet::vault(brokerSle->at(sfVaultID)), view, j_};
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultAsset = vaultSle->at(sfAsset);
@@ -411,11 +414,11 @@ LoanManage::doApply()
// Valid flag combinations are checked in preflight. No flags is valid -
// just a noop.
if (tx.isFlag(tfLoanDefault))
return defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_);
return defaultLoan(loanSle, brokerSle, vaultSle, vaultAsset);
if (tx.isFlag(tfLoanImpair))
return impairLoan(view, loanSle, vaultSle, vaultAsset, j_);
return impairLoan(loanSle, vaultSle, vaultAsset);
if (tx.isFlag(tfLoanUnimpair))
return unimpairLoan(view, loanSle, vaultSle, vaultAsset, j_);
return unimpairLoan(loanSle, vaultSle, vaultAsset);
// NoOp, as described above.
return tesSUCCESS;
}();

View File

@@ -287,7 +287,7 @@ LoanPay::doApply()
auto const amount = tx[sfAmount];
auto const loanID = tx[sfLoanID];
LoanEntry<ApplyView> loanSle{keylet::loan(loanID), view};
LoanEntry<ApplyView> loanSle{keylet::loan(loanID), view, j_};
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
std::int32_t const loanScale = loanSle->at(sfLoanScale);
@@ -299,7 +299,7 @@ LoanPay::doApply()
auto const brokerOwner = brokerSle->at(sfOwner);
auto const brokerPseudoAccount = brokerSle->at(sfAccount);
auto const vaultID = brokerSle->at(sfVaultID);
VaultEntry<ApplyView> vaultSle{keylet::vault(vaultID), view};
VaultEntry<ApplyView> vaultSle{keylet::vault(vaultID), view, j_};
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudoAccount = vaultSle->at(sfAccount);
@@ -362,7 +362,7 @@ LoanPay::doApply()
// change will be discarded.
if (loanSle->isFlag(lsfLoanImpaired))
{
if (auto const ret = LoanManage::unimpairLoan(view, loanSle, vaultSle, asset, j_))
if (auto const ret = LoanManage::unimpairLoan(loanSle, vaultSle, asset))
{
JLOG(j_.fatal()) << "Failed to unimpair loan before payment.";
return ret; // LCOV_EXCL_LINE
@@ -381,7 +381,7 @@ LoanPay::doApply()
}();
std::expected<LoanPaymentParts, TER> const paymentParts =
loanMakePayment(asset, view, loanSle, brokerSle, amount, paymentType, j_);
loanMakePayment(asset, loanSle, brokerSle, amount, paymentType);
if (!paymentParts)
{

View File

@@ -57,25 +57,11 @@ OracleDelete::deleteOracle(
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(keylet::ownerDir(account), (*sle)[sfOwnerNode], sle->key(), true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete Oracle from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
AccountRootEntry<ApplyView> sleOwner{keylet::account(account), view};
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1;
adjustOwnerCount(view, sleOwner.mutableSle(), count, j);
view.erase(sle);
return tesSUCCESS;
// Unlink the Oracle from its owner's directory, decrement the owner's
// OwnerCount by reserveCount() (1, or 2 for a large price-data series),
// and erase it. See OracleEntry.
OracleEntry<ApplyView> oracle{sle, view, j};
return oracle.destroy();
}
TER

View File

@@ -312,18 +312,10 @@ OracleSet::doApply()
sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
auto page = ctx_.view().dirInsert(
keylet::ownerDir(accountID_), sle->key(), describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
auto const count = series.size() > 5 ? 2 : 1;
if (!adjustOwnerCount(ctx_, count))
return tefINTERNAL; // LCOV_EXCL_LINE
sle.insert();
// Reserve check (owner's pre-fee balance) + link into the owner
// directory + bump the owner's OwnerCount by reserveCount() (1, or 2
// for a large price-data series) + insert. See OracleEntry.
return sle.create(preFeeBalance_);
}
return tesSUCCESS;

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -153,44 +154,17 @@ DepositPreauth::doApply()
{
if (ctx_.tx.isFieldPresent(sfAuthorize))
{
auto const sleOwner = view().peek(keylet::account(accountID_));
if (!sleOwner)
return {tefINTERNAL};
// A preauth counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
{
STAmount const reserve{
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
// Preclaim already verified that the Preauth entry does not yet exist.
// Create and populate the Preauth entry.
AccountID const auth{ctx_.tx[sfAuthorize]};
Keylet const preauthKeylet = keylet::depositPreauth(accountID_, auth);
auto slePreauth = std::make_shared<SLE>(preauthKeylet);
DepositPreauthEntry<ApplyView> slePreauth{preauthKeylet, view()};
slePreauth.newSLE();
slePreauth->setAccountID(sfAccount, accountID_);
slePreauth->setAccountID(sfAuthorize, auth);
view().insert(slePreauth);
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), preauthKeylet, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding DepositPreauth to owner directory "
<< to_string(preauthKeylet.key) << ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
slePreauth->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sleOwner, 1, j_);
return slePreauth.create(preFeeBalance_);
}
else if (ctx_.tx.isFieldPresent(sfUnauthorize))
{
@@ -200,21 +174,6 @@ DepositPreauth::doApply()
}
else if (ctx_.tx.isFieldPresent(sfAuthorizeCredentials))
{
auto const sleOwner = view().peek(keylet::account(accountID_));
if (!sleOwner)
return tefINTERNAL; // LCOV_EXCL_LINE
// A preauth counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
{
STAmount const reserve{
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
// Preclaim already verified that the Preauth entry does not yet exist.
// Create and populate the Preauth entry.
@@ -230,28 +189,13 @@ DepositPreauth::doApply()
}
Keylet const preauthKey = keylet::depositPreauth(accountID_, sortedTX);
auto slePreauth = std::make_shared<SLE>(preauthKey);
if (!slePreauth)
return tefINTERNAL; // LCOV_EXCL_LINE
DepositPreauthEntry<ApplyView> slePreauth{preauthKey, view()};
slePreauth.newSLE();
slePreauth->setAccountID(sfAccount, accountID_);
slePreauth->peekFieldArray(sfAuthorizeCredentials) = std::move(sortedLE);
view().insert(slePreauth);
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), preauthKey, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Adding DepositPreauth to owner directory " << to_string(preauthKey.key)
<< ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
slePreauth->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sleOwner, 1, j_);
return slePreauth.create(preFeeBalance_);
}
else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials))
{
@@ -274,27 +218,8 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b
return tecNO_ENTRY;
}
AccountID const account{(*slePreauth)[sfAccount]};
std::uint64_t const page{(*slePreauth)[sfOwnerNode]};
if (!view.dirRemove(keylet::ownerDir(account), page, preauthIndex, false))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete DepositPreauth from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// If we succeeded, update the DepositPreauth owner's reserve.
auto const sleOwner = view.peek(keylet::account(account));
if (!sleOwner)
return tefINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleOwner, -1, j);
// Remove DepositPreauth from ledger.
view.erase(slePreauth);
return tesSUCCESS;
DepositPreauthEntry<ApplyView> preauth{slePreauth, view, j};
return preauth.destroy();
}
void

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
@@ -138,7 +139,8 @@ PaymentChannelCreate::doApply()
// Note that we use the value from the sequence or ticket as the
// payChan sequence. For more explanation see comments in SeqProxy.h.
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqValue());
auto const slep = std::make_shared<SLE>(payChanKeylet);
PayChannelEntry<ApplyView> slep{payChanKeylet, ctx_.view()};
slep.newSLE();
// Funds held in this channel
(*slep)[sfAmount] = ctx_.tx[sfAmount];
@@ -156,32 +158,13 @@ PaymentChannelCreate::doApply()
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
}
ctx_.view().insert(slep);
// Add PayChan to owner directory
{
auto const page = ctx_.view().dirInsert(
keylet::ownerDir(account), payChanKeylet, describeOwnerDir(account));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfOwnerNode] = *page;
}
// Add PayChan to the recipient's owner directory
{
auto const page =
ctx_.view().dirInsert(keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfDestinationNode] = *page;
}
// Deduct owner's balance, increment owner count
// Deduct the channel amount from the owner's balance.
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
ctx_.view().update(sle);
return tesSUCCESS;
// Reserve check + link into the owner and destination directories + bump
// the owner's OwnerCount + insert. See PayChannelEntry::ownerDirs().
return slep.create(preFeeBalance_);
}
void

View File

@@ -53,24 +53,8 @@ PermissionedDomainDelete::doApply()
PermissionedDomainEntry<ApplyView> slePd{
keylet::permissionedDomain(ctx_.tx.at(sfDomainID)), view()};
auto const page = (*slePd)[sfOwnerNode];
if (!view().dirRemove(keylet::ownerDir(accountID_), page, slePd->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete permissioned domain directory entry.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
AccountRootEntry<ApplyView> ownerSle{keylet::account(accountID_), view()};
XRPL_ASSERT(
ownerSle && ownerSle->getFieldU32(sfOwnerCount) > 0,
"xrpl::PermissionedDomainDelete::doApply : nonzero owner count");
adjustOwnerCount(view(), ownerSle.mutableSle(), -1, ctx_.journal);
slePd.erase();
return tesSUCCESS;
return slePd.destroy();
}
void

View File

@@ -106,30 +106,20 @@ PermissionedDomainSet::doApply()
else
{
// Create new permissioned domain.
// Check reserve availability for new object creation
auto const balance = STAmount((*ownerSle)[sfBalance]).xrp();
auto const reserve = ctx_.view().fees().accountReserve((*ownerSle)[sfOwnerCount] + 1);
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
bool const fixEnabled = view().rules().enabled(fixCleanup3_1_3);
auto const seq = fixEnabled ? ctx_.tx.getSeqValue() : ctx_.tx.getFieldU32(sfSequence);
Keylet const pdKeylet = keylet::permissionedDomain(accountID_, seq);
PermissionedDomainEntry<ApplyView> slePd{pdKeylet, view()};
slePd.newSLE();
// Adopt a fresh SLE (rather than peek + newSLE) so that the pre-
// fixCleanup3_1_3 ticket case, where sfSequence is 0 and the keylet can
// collide with an existing domain, throws at insert() (-> tefEXCEPTION)
// instead of tripping the newSLE() "no existing SLE" assertion.
PermissionedDomainEntry<ApplyView> slePd{std::make_shared<SLE>(pdKeylet), view()};
slePd->setAccountID(sfOwner, accountID_);
slePd->setFieldU32(sfSequence, seq);
slePd->peekFieldArray(sfAcceptedCredentials) = std::move(sortedLE);
auto const page =
view().dirInsert(keylet::ownerDir(accountID_), pdKeylet, describeOwnerDir(accountID_));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
slePd->setFieldU64(sfOwnerNode, *page);
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), ownerSle.mutableSle(), 1, ctx_.journal);
slePd.insert();
return slePd.create(preFeeBalance_);
}
return tesSUCCESS;

View File

@@ -72,19 +72,7 @@ TicketCreate::doApply()
if (!sleAccountRoot)
return tefINTERNAL; // LCOV_EXCL_LINE
// Each ticket counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
std::uint32_t const ticketCount = ctx_.tx[sfTicketCount];
{
XRPAmount const reserve =
view().fees().accountReserve(sleAccountRoot->getFieldU32(sfOwnerCount) + ticketCount);
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
beast::Journal const viewJ{ctx_.registry.get().getJournal("View")};
// The starting ticket sequence is the same as the current account
// root sequence. Before we got here to doApply(), the transaction
@@ -107,18 +95,15 @@ TicketCreate::doApply()
sleTicket->setAccountID(sfAccount, accountID_);
sleTicket->setFieldU32(sfTicketSequence, curTicketSeq);
sleTicket.insert();
auto const page = view().dirInsert(
keylet::ownerDir(accountID_), ticketKeylet, describeOwnerDir(accountID_));
JLOG(j_.trace()) << "Creating ticket " << to_string(ticketKeylet.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
sleTicket->setFieldU64(sfOwnerNode, *page);
// Each ticket counts against the reserve of the issuing account, but
// the reserve is checked against the starting balance (preFeeBalance_)
// because we want to allow dipping into the reserve to pay fees. This
// reserve check + owner directory link + OwnerCount bump is handled by
// create(). The final ticket's create() enforces the reserve for the
// full ticketCount, since OwnerCount grows with each iteration.
if (auto const ter = sleTicket.create(preFeeBalance_); !isTesSuccess(ter))
return ter;
}
// Update the record of the number of Tickets this account owns.
@@ -126,9 +111,6 @@ TicketCreate::doApply()
sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount);
// Every added Ticket counts against the creator's reserve.
adjustOwnerCount(view(), sleAccountRoot.mutableSle(), ticketCount, viewJ);
// TicketCreate is the only transaction that can cause an account root's
// Sequence field to increase by more than one. October 2018.
sleAccountRoot->setFieldU32(sfSequence, firstTicketSeq + ticketCount);

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -115,71 +116,60 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
std::expected<MPTID, TER>
MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args)
{
auto const acct = view.peek(keylet::account(args.account));
if (!acct)
if (!view.exists(keylet::account(args.account)))
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
if (args.priorBalance &&
*(args.priorBalance) < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
return std::unexpected(tecINSUFFICIENT_RESERVE);
auto const mptId = makeMptID(args.sequence, args.account);
auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptId);
// create the MPTokenIssuance
MPTokenIssuanceEntry<ApplyView> mptIssuance{mptIssuanceKeylet, view, journal};
mptIssuance.newSLE();
(*mptIssuance)[sfFlags] = args.flags & ~tfUniversal;
(*mptIssuance)[sfIssuer] = args.account;
(*mptIssuance)[sfOutstandingAmount] = 0;
(*mptIssuance)[sfSequence] = args.sequence;
if (args.maxAmount)
(*mptIssuance)[sfMaximumAmount] = *args.maxAmount;
if (args.assetScale)
(*mptIssuance)[sfAssetScale] = *args.assetScale;
if (args.transferFee)
(*mptIssuance)[sfTransferFee] = *args.transferFee;
if (args.metadata)
(*mptIssuance)[sfMPTokenMetadata] = *args.metadata;
if (args.domainId)
(*mptIssuance)[sfDomainID] = *args.domainId;
if (args.mutableFlags)
(*mptIssuance)[sfMutableFlags] = *args.mutableFlags;
if (args.referenceHolding)
{
auto const ownerNode = view.dirInsert(
keylet::ownerDir(args.account), mptIssuanceKeylet, describeOwnerDir(args.account));
if (!ownerNode)
return std::unexpected(tecDIR_FULL); // LCOV_EXCL_LINE
auto mptIssuance = std::make_shared<SLE>(mptIssuanceKeylet);
(*mptIssuance)[sfFlags] = args.flags & ~tfUniversal;
(*mptIssuance)[sfIssuer] = args.account;
(*mptIssuance)[sfOutstandingAmount] = 0;
(*mptIssuance)[sfOwnerNode] = *ownerNode;
(*mptIssuance)[sfSequence] = args.sequence;
if (args.maxAmount)
(*mptIssuance)[sfMaximumAmount] = *args.maxAmount;
if (args.assetScale)
(*mptIssuance)[sfAssetScale] = *args.assetScale;
if (args.transferFee)
(*mptIssuance)[sfTransferFee] = *args.transferFee;
if (args.metadata)
(*mptIssuance)[sfMPTokenMetadata] = *args.metadata;
if (args.domainId)
(*mptIssuance)[sfDomainID] = *args.domainId;
if (args.mutableFlags)
(*mptIssuance)[sfMutableFlags] = *args.mutableFlags;
if (args.referenceHolding)
{
// Defensive: the holding must already exist and be of an
// expected type. Callers (currently only VaultCreate)
// populate this after the pseudo-account's MPToken /
// RippleState has been installed. A missing holding here
// would dangle the pointer and is a programmer error.
auto const sleHolding = view.read(keylet::unchecked(*args.referenceHolding));
if (!sleHolding)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
auto const type = sleHolding->getType();
if (type != ltMPTOKEN && type != ltRIPPLE_STATE)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
(*mptIssuance)[sfReferenceHolding] = *args.referenceHolding;
}
view.insert(mptIssuance);
// Defensive: the holding must already exist and be of an
// expected type. Callers (currently only VaultCreate)
// populate this after the pseudo-account's MPToken /
// RippleState has been installed. A missing holding here
// would dangle the pointer and is a programmer error.
auto const sleHolding = view.read(keylet::unchecked(*args.referenceHolding));
if (!sleHolding)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
auto const type = sleHolding->getType();
if (type != ltMPTOKEN && type != ltRIPPLE_STATE)
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
(*mptIssuance)[sfReferenceHolding] = *args.referenceHolding;
}
// Update owner count.
adjustOwnerCount(view, acct, 1, journal);
// Reserve check against the issuer's pre-fee balance (skipped when
// priorBalance is std::nullopt, i.e. VaultCreate's pseudo-account) + link
// into the issuer's owner directory + bump the issuer's OwnerCount +
// insert. See MPTokenIssuanceEntry.
if (auto const ter = mptIssuance.create(args.priorBalance); !isTesSuccess(ter))
return std::unexpected(ter);
return mptId;
}

View File

@@ -1,6 +1,7 @@
#include <xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SLEWrappers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -42,18 +43,12 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
TER
MPTokenIssuanceDestroy::doApply()
{
auto const mpt = view().peek(keylet::mptokenIssuance(ctx_.tx[sfMPTokenIssuanceID]));
MPTokenIssuanceEntry<ApplyView> mpt{
keylet::mptokenIssuance(ctx_.tx[sfMPTokenIssuanceID]), view()};
if (accountID_ != mpt->getAccountID(sfIssuer))
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view().dirRemove(keylet::ownerDir(accountID_), (*mpt)[sfOwnerNode], mpt->key(), false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
view().erase(mpt);
adjustOwnerCount(view(), view().peek(keylet::account(accountID_)), -1, j_);
return tesSUCCESS;
return mpt.destroy();
}
void

View File

@@ -67,9 +67,11 @@ struct DID_test : public beast::unit_test::Suite
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
// Pay alice almost enough to make the reserve for a DID.
env(pay(env.master, alice, drops(incReserve + 2 * baseFee - 1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve + drops(baseFee - 1));
// Pay alice almost enough to make the reserve for a DID. The reserve is
// checked against the pre-fee balance, so leave her one drop short of
// the object reserve at the point the DIDSet is evaluated.
env(pay(env.master, alice, drops(incReserve + baseFee - 1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve - drops(1));
env.close();
// alice still does not have enough XRP for the reserve of a DID.

View File

@@ -1527,10 +1527,8 @@ public:
testcase("canApplyToBrokerCover: " + tc.name);
auto broker = std::make_shared<SLE>(Keylet{ltLOAN_BROKER, uint256{1u}});
broker->at(sfCoverAvailable) = tc.coverAvailable;
LoanBrokerEntry<ReadView> sle{broker, *env.current()};
BEAST_EXPECT(
canApplyToBrokerCover(*env.current(), sle, iou, tc.amount, env.journal, "test") ==
tc.expected);
LoanBrokerEntry<ReadView> sle{broker, *env.current(), env.journal};
BEAST_EXPECT(canApplyToBrokerCover(sle, iou, tc.amount, "test") == tc.expected);
}
// Amendment off → guard is bypassed regardless of amount.
@@ -1539,15 +1537,9 @@ public:
Env const envOff{*this, testableAmendments() - fixCleanup3_2_0};
auto broker = std::make_shared<SLE>(Keylet{ltLOAN_BROKER, uint256{1u}});
broker->at(sfCoverAvailable) = Number{10};
LoanBrokerEntry<ReadView> sle{broker, *envOff.current()};
LoanBrokerEntry<ReadView> sle{broker, *envOff.current(), envOff.journal};
BEAST_EXPECT(
canApplyToBrokerCover(
*envOff.current(),
sle,
iou,
STAmount{iou, Number{0}},
envOff.journal,
"test") == tesSUCCESS);
canApplyToBrokerCover(sle, iou, STAmount{iou, Number{0}}, "test") == tesSUCCESS);
}
}

View File

@@ -509,9 +509,11 @@ class PermissionedDomains_test : public beast::unit_test::Suite
auto const baseFee = env.current()->fees().base.drops();
// Pay alice almost enough to make the reserve.
env(pay(env.master, alice, incReserve + drops(2 * baseFee) - drops(1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve + drops(baseFee) - drops(1));
// Pay alice almost enough to make the reserve. The reserve is checked
// against the pre-fee balance, so leave her one drop short of the object
// reserve at the point the PermissionedDomainSet is evaluated.
env(pay(env.master, alice, incReserve + drops(baseFee) - drops(1)));
BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve - drops(1));
env.close();
// alice still does not have enough XRP for the reserve.