mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-29 10:00:30 +00:00
add create/destroy helpers
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user