diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index 04f28e7ddd..e382ba105a 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -48,11 +48,9 @@ namespace xrpl { */ [[nodiscard]] TER canApplyToBrokerCover( - ReadView const& view, LoanBrokerEntry 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 loanMakePayment( Asset const& asset, - ApplyView& view, LoanEntry& loan, LoanBrokerEntry const& brokerSle, STAmount const& amount, - LoanPaymentType const paymentType, - beast::Journal j); + LoanPaymentType const paymentType); } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/SLEBase.h b/include/xrpl/ledger/helpers/SLEBase.h index e2539e1517..3344d4ba94 100644 --- a/include/xrpl/ledger/helpers/SLEBase.h +++ b/include/xrpl/ledger/helpers/SLEBase.h @@ -3,12 +3,22 @@ #include #include #include +#include // adjustOwnerCount +#include // describeOwnerDir +#include +#include // keylet::account, keylet::ownerDir +#include #include +#include +#include #include +#include #include +#include #include #include +#include namespace xrpl { @@ -16,6 +26,23 @@ namespace xrpl { template concept WritableView = std::derived_from; +/** 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(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 + 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 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(reserveCount()), j_); + } + + view_.erase(sle_); + return tesSUCCESS; + } + [[nodiscard]] beast::Journal journal() const { diff --git a/include/xrpl/ledger/helpers/SLEWrappers.h b/include/xrpl/ledger/helpers/SLEWrappers.h index 2c4d29aa39..e7400fde2f 100644 --- a/include/xrpl/ledger/helpers/SLEWrappers.h +++ b/include/xrpl/ledger/helpers/SLEWrappers.h @@ -56,6 +56,19 @@ public: : SLEBase(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 + ownerDirs() const override + { + auto const owner = this->sle()->getAccountID(sfAccount); + auto const dest = this->sle()->getAccountID(sfDestination); + std::vector dirs{{owner, &sfOwnerNode, /*countsToward=*/true}}; + if (dest != owner) + dirs.push_back({dest, &sfDestinationNode, /*countsToward=*/false}); + return dirs; + } }; template @@ -73,6 +86,12 @@ public: : SLEBase(keylet::did(account), view, j) { } + + [[nodiscard]] SField const& + ownerField() const override + { + return sfAccount; + } }; template @@ -142,6 +161,12 @@ public: : SLEBase(keylet::ticket(id, ticketSeq), view, j) { } + + [[nodiscard]] SField const& + ownerField() const override + { + return sfAccount; + } }; template @@ -262,6 +287,12 @@ public: : SLEBase(keylet::depositPreauth(owner, preauthorized), view, j) { } + + [[nodiscard]] SField const& + ownerField() const override + { + return sfAccount; + } }; template @@ -351,6 +382,29 @@ public: : SLEBase(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 + 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 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()) + dirs.push_back({issuer, &sfIssuerNode, /*countsToward=*/false}); + return dirs; + } }; template @@ -370,6 +424,16 @@ public: : SLEBase(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 + ownerDirs() const override + { + return { + {this->sle()->getAccountID(sfAccount), &sfOwnerNode, /*countsToward=*/true}, + {this->sle()->getAccountID(sfDestination), &sfDestinationNode, /*countsToward=*/false}}; + } }; template @@ -406,6 +470,12 @@ public: : SLEBase(keylet::mptokenIssuance(seq, issuer), view, j) { } + + [[nodiscard]] SField const& + ownerField() const override + { + return sfIssuer; + } }; template @@ -442,6 +512,19 @@ public: : SLEBase(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 @@ -461,6 +544,26 @@ public: : SLEBase(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 + 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 dirs; + dirs.push_back({issuer, &sfIssuerNode, /*countsToward=*/!accepted || subject == issuer}); + if (subject != issuer) + dirs.push_back({subject, &sfSubjectNode, /*countsToward=*/accepted}); + return dirs; + } }; template @@ -479,6 +582,12 @@ public: : SLEBase(keylet::permissionedDomain(account, seq), view, j) { } + + [[nodiscard]] SField const& + ownerField() const override + { + return sfOwner; + } }; template @@ -497,6 +606,16 @@ public: : SLEBase(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 + ownerDirs() const override + { + return { + {this->sle()->getAccountID(sfAccount), &sfOwnerNode, /*countsToward=*/true}, + {this->sle()->getAccountID(sfAuthorize), &sfDestinationNode, /*countsToward=*/false}}; + } }; template diff --git a/include/xrpl/tx/transactors/lending/LoanManage.h b/include/xrpl/tx/transactors/lending/LoanManage.h index f420b30f29..5c8aa8872a 100644 --- a/include/xrpl/tx/transactors/lending/LoanManage.h +++ b/include/xrpl/tx/transactors/lending/LoanManage.h @@ -41,32 +41,26 @@ public: */ static TER defaultLoan( - ApplyView& view, LoanEntry& loanSle, LoanBrokerEntry& brokerSle, VaultEntry& 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& loanSle, VaultEntry& 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& loanSle, VaultEntry& vaultSle, - Asset const& vaultAsset, - beast::Journal j); + Asset const& vaultAsset); TER doApply() override; diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 86cf4225c1..4d3e3fa2c7 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -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 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 cred{sleCredential, view, j}; + return cred.destroy(); } NotTEC diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 13a37b09e3..6cb05d7f85 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -32,13 +32,14 @@ namespace xrpl { [[nodiscard]] TER canApplyToBrokerCover( - ReadView const& view, LoanBrokerEntry 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 loanMakePayment( Asset const& asset, - ApplyView& view, LoanEntry& loan, LoanBrokerEntry 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); diff --git a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp index b2fb70d41a..4a2d4aff33 100644 --- a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp @@ -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 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 diff --git a/src/libxrpl/tx/transactors/check/CheckCancel.cpp b/src/libxrpl/tx/transactors/check/CheckCancel.cpp index ed19159abb..a339face1e 100644 --- a/src/libxrpl/tx/transactors/check/CheckCancel.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCancel.cpp @@ -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 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 diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index a168941c50..1c42ea9f47 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -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 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 checkEntry{sleCheck, psb}; + if (auto const ter = checkEntry.destroy(); !isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE psb.apply(ctx_.rawView()); return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index d780ffbeae..0408f886b3 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -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 diff --git a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp index c7a8ad42f0..1b3acc12a5 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp @@ -128,17 +128,6 @@ CredentialCreate::doApply() sleCred->setFieldU32(sfExpiration, *optExp); } - AccountRootEntry 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 diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index 3b6674095d..f057bd69e2 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -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 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 delegate{sle, view, j}; + return delegate.destroy(); } void diff --git a/src/libxrpl/tx/transactors/did/DIDDelete.cpp b/src/libxrpl/tx/transactors/did/DIDDelete.cpp index 09a8a1c2c1..94bd7a0fab 100644 --- a/src/libxrpl/tx/transactors/did/DIDDelete.cpp +++ b/src/libxrpl/tx/transactors/did/DIDDelete.cpp @@ -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 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 did{sle, view, j}; + return did.destroy(); } TER diff --git a/src/libxrpl/tx/transactors/did/DIDSet.cpp b/src/libxrpl/tx/transactors/did/DIDSet.cpp index a8b716ed71..a7d43450a2 100644 --- a/src/libxrpl/tx/transactors/did/DIDSet.cpp +++ b/src/libxrpl/tx/transactors/did/DIDSet.cpp @@ -63,39 +63,6 @@ DIDSet::preflight(PreflightContext const& ctx) return tesSUCCESS; } -static TER -addSLE(ApplyContext& ctx, DIDEntry& sle, AccountID const& owner) -{ - AccountRootEntry 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 diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 91670f6f48..fb3c9fe9a9 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -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 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 diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 9c6624ff0e..7229e03e71 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -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()) - { - 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 diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 02f41bf9b5..e9fb6186f9 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -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 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 diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 0d3e0d72d0..50b9eba55f 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -303,11 +303,9 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx) STAmount const& clawAmount = *findClawAmount; if (auto const ret = canApplyToBrokerCover( - ctx.view, - LoanBrokerEntry{sleBroker, ctx.view}, + LoanBrokerEntry{sleBroker, ctx.view, ctx.j}, vaultAsset, clawAmount, - ctx.j, "LoanBrokerCoverClawback")) return ret; diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 9d35564218..97743516c1 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -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{sleBroker, ctx.view}, + LoanBrokerEntry{sleBroker, ctx.view, ctx.j}, vaultAsset, amount, - ctx.j, "LoanBrokerCoverWithdraw")) return ret; diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index 97f1a9faaf..6f1486dbe2 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -146,13 +146,14 @@ owedToVault(LoanEntry const& loanSle) TER LoanManage::defaultLoan( - ApplyView& view, LoanEntry& loanSle, LoanBrokerEntry& brokerSle, VaultEntry& 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& loanSle, VaultEntry& 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& loanSle, VaultEntry& 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 loanSle{keylet::loan(loanID), view}; + LoanEntry loanSle{keylet::loan(loanID), view, j_}; if (!loanSle) return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const brokerID = loanSle->at(sfLoanBrokerID); - LoanBrokerEntry brokerSle{keylet::loanBroker(brokerID), view}; + LoanBrokerEntry brokerSle{keylet::loanBroker(brokerID), view, j_}; if (!brokerSle) return tefBAD_LEDGER; // LCOV_EXCL_LINE - VaultEntry vaultSle{keylet::vault(brokerSle->at(sfVaultID)), view}; + VaultEntry 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; }(); diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index 6832b60237..391aa3ed52 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -287,7 +287,7 @@ LoanPay::doApply() auto const amount = tx[sfAmount]; auto const loanID = tx[sfLoanID]; - LoanEntry loanSle{keylet::loan(loanID), view}; + LoanEntry 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 vaultSle{keylet::vault(vaultID), view}; + VaultEntry 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 const paymentParts = - loanMakePayment(asset, view, loanSle, brokerSle, amount, paymentType, j_); + loanMakePayment(asset, loanSle, brokerSle, amount, paymentType); if (!paymentParts) { diff --git a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp index 066a1bcaa0..4818d4b737 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp @@ -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 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 oracle{sle, view, j}; + return oracle.destroy(); } TER diff --git a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp index 6af16a2d95..bce8df9b43 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp @@ -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; diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index 7e950dc743..5e95b630c5 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -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(preauthKeylet); + DepositPreauthEntry 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(preauthKey); - if (!slePreauth) - return tefINTERNAL; // LCOV_EXCL_LINE + DepositPreauthEntry 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 preauth{slePreauth, view, j}; + return preauth.destroy(); } void diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index 977da56861..453a32b93b 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -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(payChanKeylet); + PayChannelEntry 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 diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp index 241b175aa1..6d67bd2e15 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp @@ -53,24 +53,8 @@ PermissionedDomainDelete::doApply() PermissionedDomainEntry 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 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 diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp index 220620fa47..f1df11ae6a 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp @@ -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 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 slePd{std::make_shared(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; diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index d4f4fc6a52..54d566df26 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -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); diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index e30127b688..6cd3202683 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -115,71 +116,60 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) std::expected 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 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(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; } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp index c56697767b..fe19317c5a 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -42,18 +43,12 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx) TER MPTokenIssuanceDestroy::doApply() { - auto const mpt = view().peek(keylet::mptokenIssuance(ctx_.tx[sfMPTokenIssuanceID])); + MPTokenIssuanceEntry 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 diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index 496dfed059..7eacf22195 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -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. diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index 2062f69b4e..cbc11a15d1 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -1527,10 +1527,8 @@ public: testcase("canApplyToBrokerCover: " + tc.name); auto broker = std::make_shared(Keylet{ltLOAN_BROKER, uint256{1u}}); broker->at(sfCoverAvailable) = tc.coverAvailable; - LoanBrokerEntry sle{broker, *env.current()}; - BEAST_EXPECT( - canApplyToBrokerCover(*env.current(), sle, iou, tc.amount, env.journal, "test") == - tc.expected); + LoanBrokerEntry 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(Keylet{ltLOAN_BROKER, uint256{1u}}); broker->at(sfCoverAvailable) = Number{10}; - LoanBrokerEntry sle{broker, *envOff.current()}; + LoanBrokerEntry 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); } } diff --git a/src/test/app/PermissionedDomains_test.cpp b/src/test/app/PermissionedDomains_test.cpp index 784c2b4f56..45f685c9c7 100644 --- a/src/test/app/PermissionedDomains_test.cpp +++ b/src/test/app/PermissionedDomains_test.cpp @@ -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.