1#include <xrpl/ledger/helpers/MPTokenHelpers.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/ledger/ApplyView.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/View.h>
10#include <xrpl/ledger/helpers/AccountRootHelpers.h>
11#include <xrpl/ledger/helpers/CredentialHelpers.h>
12#include <xrpl/ledger/helpers/DirectoryHelpers.h>
13#include <xrpl/ledger/helpers/SponsorHelpers.h>
14#include <xrpl/ledger/helpers/TokenHelpers.h>
15#include <xrpl/protocol/AccountID.h>
16#include <xrpl/protocol/Feature.h>
17#include <xrpl/protocol/Indexes.h>
18#include <xrpl/protocol/Issue.h>
19#include <xrpl/protocol/LedgerFormats.h>
20#include <xrpl/protocol/MPTIssue.h>
21#include <xrpl/protocol/Protocol.h>
22#include <xrpl/protocol/Rate.h>
23#include <xrpl/protocol/SField.h>
24#include <xrpl/protocol/STAmount.h>
25#include <xrpl/protocol/STLedgerEntry.h>
26#include <xrpl/protocol/TER.h>
27#include <xrpl/protocol/TxFlags.h>
28#include <xrpl/protocol/UintTypes.h>
29#include <xrpl/protocol/XRPAmount.h>
53 issuanceSle.
getType() == ltMPTOKEN_ISSUANCE,
"xrpl::isGlobalFrozen : MPTokenIssuance SLE");
55 return issuanceSle.
isFlag(lsfMPTLocked);
69 XRPL_ASSERT(mptSle.
getType() == ltMPTOKEN,
"xrpl::isIndividualFrozen : MPToken SLE");
71 return mptSle.
isFlag(lsfMPTLocked);
90 "xrpl::isFrozen : MPToken or MPTokenIssuance SLE");
94 XRPL_ASSERT(sle[sfAccount] == account,
"xrpl::isFrozen : valid MPToken holder");
96 MPTID const mptID = sle[sfMPTokenIssuanceID];
108 MPTIssue const mptIssue{sle[sfSequence], sle[sfIssuer]};
124 for (
auto const& account : accounts)
132 auto const anyVaultFrozen = [&](
auto const& shareOrIssuance) {
138 return issuanceSle ? anyVaultFrozen(*issuanceSle) : anyVaultFrozen(mptIssue);
148 sle && sle->isFieldPresent(sfTransferFee))
150 auto const fee = sle->getFieldU16(sfTransferFee);
151 XRPL_ASSERT(fee <=
kMaxTransferFee,
"xrpl::transferRate : fee is too large");
152 return Rate{1'000'000'000u + (10'000 * fee)};
167 if (!issuance->isFlag(lsfMPTCanTransfer))
183 auto const& mptID = mptIssue.
getMptID();
189 if (mpt->isFlag(lsfMPTLocked))
203 MPTID const& mptIssuanceID,
221 if ((flags & tfMPTUnauthorize) != 0u)
224 auto const sleMpt = ctx.
view.
peek(mptokenKey);
225 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
227 (*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
247 return sponsorExp.error();
248 auto const sponsorSle = *sponsorExp;
258 if (sponsorSle ||
ownerCount(sleAcct, journal) >= 2)
261 ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
268 if (!mpt || mpt->getAccountID(sfIssuer) == account)
271 UNREACHABLE(
"xrpl::authorizeMPToken : invalid issuance or issuers token");
279 if (
auto ter =
dirLink(ctx.
view, account, mptoken))
282 (*mptoken)[sfAccount] = account;
283 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
284 (*mptoken)[sfFlags] = 0;
301 if (account != (*sleMptIssuance)[sfIssuer])
313 if ((flags & tfMPTUnauthorize) != 0u)
315 flagsOut &= ~lsfMPTAuthorized;
321 flagsOut |= lsfMPTAuthorized;
324 if (flagsIn != flagsOut)
325 sleMpt->setFieldU32(sfFlags, flagsOut);
341 bool const accountIsIssuer = accountID == mptIssue.
getIssuer();
342 auto const& mptID = mptIssue.
getMptID();
350 if (mptoken->at(sfMPTAmount) != 0 ||
351 (ctx.
view.
rules().
enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
355 if (mptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
356 mptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
357 mptoken->isFieldPresent(sfIssuerEncryptedBalance) ||
358 mptoken->isFieldPresent(sfAuditorEncryptedBalance))
381 bool const fix330Enabled = view.
rules().
enabled(fixCleanup3_3_0);
382 bool const featureSAVEnabled = view.
rules().
enabled(featureSingleAssetVault);
383 bool const featureMPTV2Enabled = view.
rules().
enabled(featureMPTokensV2);
388 auto const isPseudoAccountExempt = [&] {
389 return (featureSAVEnabled || featureMPTV2Enabled) &&
isPseudoAccount(view, account);
393 auto const sleIssuance = view.
read(mptID);
397 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
400 if (mptIssuer == account)
404 if (fix330Enabled && isPseudoAccountExempt())
407 if (featureSAVEnabled)
412 UNREACHABLE(
"xrpl::MPTokenHelpers::requireAuth : reached asset check depth");
422 if (sleIssuer->isFieldPresent(sfVaultID))
424 auto const sleVault = view.
read(
keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
428 auto const asset = sleVault->at(sfAsset);
429 if (
auto const err = asset.visit(
430 [&](
Issue const& issue) { return requireAuth(view, issue, account, authType); },
432 return requireAuth(view, issue, account, authType, depth + 1);
440 auto const sleToken = view.
read(mptokenID);
448 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
452 sleIssuance->isFlag(lsfMPTRequireAuth),
453 "xrpl::requireAuth : issuance requires authorization");
469 if (!fix330Enabled && isPseudoAccountExempt())
473 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
474 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
483 MPTID const& mptIssuanceID,
493 sleIssuance->isFlag(lsfMPTRequireAuth),
494 "xrpl::enforceMPTokenAuthorization : authorization required");
496 if (account == sleIssuance->at(sfIssuer))
501 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
502 bool expired =
false;
503 bool const authorizedByDomain = [&]() ->
bool {
505 if (!maybeDomainID.has_value())
516 if (!authorizedByDomain && sleToken ==
nullptr)
527 if (!authorizedByDomain && maybeDomainID.has_value())
534 if (!authorizedByDomain)
539 sleToken !=
nullptr && !maybeDomainID.has_value(),
540 "xrpl::enforceMPTokenAuthorization : found MPToken");
541 if (sleToken->isFlag(lsfMPTAuthorized))
546 if (authorizedByDomain && sleToken !=
nullptr)
551 maybeDomainID.has_value(),
552 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
555 if (authorizedByDomain)
560 maybeDomainID.has_value() && sleToken ==
nullptr,
561 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
575 UNREACHABLE(
"xrpl::enforceMPTokenAuthorization : condition list is incomplete");
584 sleHolding.
getType() == ltRIPPLE_STATE || sleHolding.
getType() == ltMPTOKEN,
585 "xrpl::assetOfHolding",
586 "unexpected holding type");
588 sleShareIssuance.
getType() == ltMPTOKEN_ISSUANCE,
589 "xrpl::assetOfHolding",
590 "not SLE MPTokenIssuance");
592 if (sleHolding.
getType() == ltMPTOKEN)
595 auto const vaultPseudo = sleShareIssuance.
at(sfIssuer);
598 auto const& iouIssuer =
599 (lowLimit.getIssuer() != vaultPseudo) ? lowLimit.
getIssuer() : highLimit.getIssuer();
600 return Issue{lowLimit.get<
Issue>().currency, iouIssuer};
613 auto const sleIssuance = view.
read(mptID);
617 auto const issuer = (*sleIssuance)[sfIssuer];
621 if (!sleIssuance->isFlag(lsfMPTCanTransfer))
631 if (view.
rules().
enabled(fixCleanup3_2_0) && sleIssuance->isFieldPresent(sfReferenceHolding))
638 UNREACHABLE(
"xrpl::MPTokenHelpers::canTransfer : reached asset check depth");
643 auto const sleHolding =
669 if (!sleIssuance->isFlag(lsfMPTCanTrade))
676 sleIssuance->isFieldPresent(sfReferenceHolding))
684 UNREACHABLE(
"xrpl::MPTokenHelpers::canTrade : reached asset check depth");
688 auto const sleHolding =
721 auto sleIssuance = view.
peek(mptID);
724 JLOG(j.
error()) <<
"lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
730 JLOG(j.
error()) <<
"lockEscrowMPT: sender is the issuer, cannot lock MPTs.";
738 auto sle = view.
peek(mptokenID);
741 JLOG(j.
error()) <<
"lockEscrowMPT: MPToken not found for " << sender;
746 auto const pay = amount.
mpt().
value();
751 JLOG(j.
error()) <<
"lockEscrowMPT: insufficient MPTAmount for " <<
to_string(sender)
752 <<
": " << amt <<
" < " << pay;
756 (*sle)[sfMPTAmount] = amt - pay;
759 uint64_t
const locked = (*sle)[~sfLockedAmount].valueOr(0);
763 JLOG(j.
error()) <<
"lockEscrowMPT: overflow on locked amount for " <<
to_string(sender)
764 <<
": " << locked <<
" + " << pay;
768 if (sle->isFieldPresent(sfLockedAmount))
770 (*sle)[sfLockedAmount] += pay;
774 sle->setFieldU64(sfLockedAmount, pay);
783 uint64_t
const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].valueOr(0);
784 auto const pay = amount.
mpt().
value();
789 JLOG(j.
error()) <<
"lockEscrowMPT: overflow on issuance "
791 << mptIssue.getMptID() <<
": " << issuanceEscrowed <<
" + " << pay;
795 if (sleIssuance->isFieldPresent(sfLockedAmount))
797 (*sleIssuance)[sfLockedAmount] += pay;
801 sleIssuance->setFieldU64(sfLockedAmount, pay);
820 netAmount == grossAmount,
821 "xrpl::unlockEscrowMPT : netAmount == grossAmount");
823 auto const& issuer = netAmount.
getIssuer();
826 auto sleIssuance = view.
peek(mptID);
829 JLOG(j.
error()) <<
"unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
835 if (!sleIssuance->isFieldPresent(sfLockedAmount))
837 JLOG(j.
error()) <<
"unlockEscrowMPT: no locked amount in issuance for "
838 << mptIssue.getMptID();
842 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
843 auto const redeem = grossAmount.
mpt().
value();
848 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient locked amount for "
849 << mptIssue.getMptID() <<
": " << locked <<
" < " << redeem;
853 auto const newLocked = locked - redeem;
856 sleIssuance->makeFieldAbsent(sfLockedAmount);
860 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
865 if (issuer != receiver)
869 auto sle = view.
peek(mptokenID);
872 JLOG(j.
error()) <<
"unlockEscrowMPT: MPToken not found for " << receiver;
877 auto delta = netAmount.
mpt().
value();
882 JLOG(j.
error()) <<
"unlockEscrowMPT: overflow on MPTAmount for " <<
to_string(receiver)
883 <<
": " << current <<
" + " << delta;
887 (*sle)[sfMPTAmount] += delta;
893 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
894 auto const redeem = netAmount.
mpt().
value();
899 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient outstanding amount for "
900 << mptIssue.getMptID() <<
": " << outstanding <<
" < " << redeem;
904 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
908 if (issuer == sender)
910 JLOG(j.
error()) <<
"unlockEscrowMPT: sender is the issuer, "
911 "cannot unlock MPTs.";
916 auto sle = view.
peek(mptokenID);
919 JLOG(j.
error()) <<
"unlockEscrowMPT: MPToken not found for " << sender;
923 if (!sle->isFieldPresent(sfLockedAmount))
925 JLOG(j.
error()) <<
"unlockEscrowMPT: no locked amount in MPToken for " <<
to_string(sender);
929 auto const locked = sle->
getFieldU64(sfLockedAmount);
930 auto const delta = grossAmount.
mpt().
value();
935 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient locked amount for " <<
to_string(sender)
936 <<
": " << locked <<
" < " << delta;
940 auto const newLocked = locked - delta;
943 sle->makeFieldAbsent(sfLockedAmount);
947 sle->setFieldU64(sfLockedAmount, newLocked);
958 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
962 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient outstanding amount for "
963 << mptIssue.getMptID() <<
": " << outstanding <<
" < " << diff;
967 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
976 MPTID const& mptIssuanceID,
983 auto const ownerNode =
990 (*mptoken)[sfAccount] = account;
991 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
992 (*mptoken)[sfFlags] = flags;
993 (*mptoken)[sfOwnerNode] = *ownerNode;
1016 if (!view.
exists(mptokenID))
1054 auto const outstanding = sleIssuance[sfOutstandingAmount];
1055 return max - outstanding;
1077 return (sendAmount > maximumAmount || outstandingAmount > (limit - sendAmount));
A generic endpoint for log messages.
Writeable view to a ledger, for applying a transaction.
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void insert(SLE::Ref sle)=0
Insert a new state SLE.
virtual void issuerSelfDebitHookMPT(MPTIssue const &issue, std::uint64_t amount, std::int64_t origBalance)
Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, UInt256 const &key, std::function< void(SLE::Ref)> const &describe)
Insert an entry to a directory.
bool dirRemove(Keylet const &directory, std::uint64_t page, UInt256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(SLE::Ref sle)=0
Remove a peeked SLE.
virtual void update(SLE::Ref sle)=0
Indicate changes to a peeked SLE.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
constexpr bool holds() const
A currency issued by an account.
constexpr value_type value() const
Returns the underlying value.
constexpr MPTID const & getMptID() const
AccountID const & getIssuer() const
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual STAmount balanceHookSelfIssueMPT(MPTIssue const &issue, std::int64_t amount) const
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
constexpr TIss const & get() const
AccountID const & getIssuer() const
LedgerEntryType getType() const
std::shared_ptr< STLedgerEntry > const & Ref
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
UInt192 getFieldH192(SField const &field) const
bool isFlag(std::uint32_t) const
std::uint64_t getFieldU64(SField const &field) const
STAmount const & getFieldAmount(SField const &field) const
TER validDomain(ReadView const &view, UInt256 domainID, AccountID const &subject)
Keylet computation functions.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet unchecked(UInt256 const &key) noexcept
Any ledger entry.
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
TER enforceMPTokenAuthorization(ApplyViewContext ctx, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
std::int64_t maxMPTAmount(SLE const &sleIssuance)
std::uint32_t ownerCount(SLE::ConstRef sle, beast::Journal j, std::int32_t ownerCountAdj=0)
Return number of the objects which reserve is covered by the account(sle) (so called "ownercount").
TER checkCreateMPT(xrpl::ApplyView &view, xrpl::MPTIssue const &mptIssue, xrpl::AccountID const &holder, SLE::Ref sponsorSle, std::uint32_t flags, beast::Journal j)
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
Returns true if account's MPToken for mptIssue carries the individual-lock flag (lsfMPTLocked).
AllowMPTOverflow
Controls whether accountSend is allowed to overflow OutstandingAmount *.
TER removeEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, MPTIssue const &mptIssue, beast::Journal journal)
TER lockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
TER addEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
std::string transHuman(TER code)
TER checkReserve(ApplyViewContext ctx, SLE::ConstRef accSle, XRPAmount accBalance, SLE::ConstRef sponsorSle, Adjustment adj, beast::Journal j, TER insufReserveCode=tecINSUFFICIENT_RESERVE)
Check if an account has sufficient reserve.
TER canMPTTradeAndTransfer(ReadView const &v, Asset const &asset, AccountID const &from, AccountID const &to)
Convenience to combine canTrade/Transfer.
Asset assetOfHolding(SLE const &sleShareIssuance, SLE const &sleHolding)
Resolve the underlying asset of a vault share.
TER unlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
std::expected< SLE::pointer, TER > getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::ConstRef accountSle)
The transaction's reserve sponsor for the given account, if applicable.
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to, WaiveMPTCanTransfer waive=WaiveMPTCanTransfer::No, std::uint8_t depth=0)
Check whether to may receive the given MPT from from.
void increaseOwnerCount(ApplyView &view, SLE::Ref accountSle, SLE::Ref sponsorSle, std::uint32_t count, beast::Journal j)
Increase owner-count fields when the caller supplies the sponsor.
void addSponsorToLedgerEntry(SLE::Ref sle, SLE::ConstRef sponsorSle, SF_ACCOUNT const &field=sfSponsor)
Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE.
std::string to_string(BaseUInt< Bits, Tag > const &a)
TER canTrade(ReadView const &view, Asset const &asset, std::uint8_t depth=0)
Check whether asset may be traded on the DEX.
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, std::uint8_t depth)
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
bool canAdd(STAmount const &amt1, STAmount const &amt2)
Safely checks if two STAmount values can be added without overflow, underflow, or precision loss.
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
constexpr std::uint8_t kMaxAssetCheckDepth
Maximum recursion depth for vault shares being put as an asset inside another vault; counted from 0.
void decreaseOwnerCountForObject(ApplyView &view, SLE::Ref accountSle, SLE::Ref objectSle, std::uint32_t count, beast::Journal j)
Decrease owner-count fields for an existing ledger object.
TER dirLink(ApplyView &view, AccountID const &owner, SLE::pointer &object, SF_UINT64 const &node=sfOwnerNode)
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
TER verifyValidDomain(ApplyView &view, AccountID const &account, UInt256 domainID, beast::Journal j)
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Rate const kParityRate
A transfer rate signifying a 1:1 exchange.
std::int64_t availableMPTAmount(SLE const &sleIssuance)
WaiveMPTCanTransfer
Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs.
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
Returns true if account cannot send or receive tokens of mptIssue because a freeze applies.
bool isPseudoAccount(SLE::const_pointer sleAcct)
Returns true if and only if sleAcct is a pseudo-account of any kind (i.e.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
constexpr std::uint16_t kMaxTransferFee
The maximum token transfer fee allowed.
bool canSubtract(STAmount const &amt1, STAmount const &amt2)
Determines if it is safe to subtract one STAmount from another.
bool isTesSuccess(TER x) noexcept
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
TERSubset< CanCvtToTER > TER
TER authorizeMPToken(ApplyViewContext ctx, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
void issuerSelfDebitHookMPT(ApplyView &view, MPTIssue const &issue, std::uint64_t amount)
Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
TER createMPToken(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, SLE::Ref sponsorSle, std::uint32_t const flags)
std::function< void(SLE::Ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
bool isMPTOverflow(std::int64_t sendAmount, std::uint64_t outstandingAmount, std::int64_t maximumAmount, AllowMPTOverflow allowOverflow)
Checks for two types of OutstandingAmount overflow during a send operation.
STAmount issuerFundsToSelfIssue(ReadView const &view, MPTIssue const &issue)
Determine funds available for an issuer to sell in an issuer owned offer.
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Bundles the mutable ledger view and the transaction being applied.
Represents a transfer rate.