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>
45 return sle->isFlag(lsfMPTLocked);
53 return sle->isFlag(lsfMPTLocked);
78 for (
auto const& account : accounts)
96 sle && sle->isFieldPresent(sfTransferFee))
98 auto const fee = sle->getFieldU16(sfTransferFee);
99 XRPL_ASSERT(fee <=
kMaxTransferFee,
"xrpl::transferRate : fee is too large");
100 return Rate{1'000'000'000u + (10'000 * fee)};
115 if (!issuance->isFlag(lsfMPTCanTransfer))
131 auto const& mptID = mptIssue.
getMptID();
135 if (mpt->isFlag(lsfMPTLocked))
149 MPTID const& mptIssuanceID,
167 if ((flags & tfMPTUnauthorize) != 0u)
170 auto const sleMpt = ctx.
view.
peek(mptokenKey);
171 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
173 (*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
193 return sponsorExp.error();
194 auto const sponsorSle = *sponsorExp;
204 if (sponsorSle ||
ownerCount(sleAcct, journal) >= 2)
207 ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
214 if (!mpt || mpt->getAccountID(sfIssuer) == account)
217 UNREACHABLE(
"xrpl::authorizeMPToken : invalid issuance or issuers token");
225 if (
auto ter =
dirLink(ctx.
view, account, mptoken))
228 (*mptoken)[sfAccount] = account;
229 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
230 (*mptoken)[sfFlags] = 0;
247 if (account != (*sleMptIssuance)[sfIssuer])
259 if ((flags & tfMPTUnauthorize) != 0u)
261 flagsOut &= ~lsfMPTAuthorized;
267 flagsOut |= lsfMPTAuthorized;
270 if (flagsIn != flagsOut)
271 sleMpt->setFieldU32(sfFlags, flagsOut);
287 bool const accountIsIssuer = accountID == mptIssue.
getIssuer();
288 auto const& mptID = mptIssue.
getMptID();
296 if (mptoken->at(sfMPTAmount) != 0 ||
297 (ctx.
view.
rules().
enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
301 if (mptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
302 mptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
303 mptoken->isFieldPresent(sfIssuerEncryptedBalance) ||
304 mptoken->isFieldPresent(sfAuditorEncryptedBalance))
327 bool const fix330Enabled = view.
rules().
enabled(fixCleanup3_3_0);
328 bool const featureSAVEnabled = view.
rules().
enabled(featureSingleAssetVault);
329 bool const featureMPTV2Enabled = view.
rules().
enabled(featureMPTokensV2);
334 auto const isPseudoAccountExempt = [&] {
335 return (featureSAVEnabled || featureMPTV2Enabled) &&
336 isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID, &sfAMMID});
340 auto const sleIssuance = view.
read(mptID);
344 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
347 if (mptIssuer == account)
351 if (fix330Enabled && isPseudoAccountExempt())
354 if (featureSAVEnabled)
359 UNREACHABLE(
"xrpl::MPTokenHelpers::requireAuth : reached asset check depth");
369 if (sleIssuer->isFieldPresent(sfVaultID))
371 auto const sleVault = view.
read(
keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
375 auto const asset = sleVault->at(sfAsset);
376 if (
auto const err = asset.visit(
377 [&](
Issue const& issue) { return requireAuth(view, issue, account, authType); },
379 return requireAuth(view, issue, account, authType, depth + 1);
387 auto const sleToken = view.
read(mptokenID);
395 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
399 sleIssuance->isFlag(lsfMPTRequireAuth),
400 "xrpl::requireAuth : issuance requires authorization");
416 if (!fix330Enabled && isPseudoAccountExempt())
420 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
421 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
430 MPTID const& mptIssuanceID,
440 sleIssuance->isFlag(lsfMPTRequireAuth),
441 "xrpl::enforceMPTokenAuthorization : authorization required");
443 if (account == sleIssuance->at(sfIssuer))
448 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
449 bool expired =
false;
450 bool const authorizedByDomain = [&]() ->
bool {
452 if (!maybeDomainID.has_value())
463 if (!authorizedByDomain && sleToken ==
nullptr)
474 if (!authorizedByDomain && maybeDomainID.has_value())
481 if (!authorizedByDomain)
486 sleToken !=
nullptr && !maybeDomainID.has_value(),
487 "xrpl::enforceMPTokenAuthorization : found MPToken");
488 if (sleToken->isFlag(lsfMPTAuthorized))
493 if (authorizedByDomain && sleToken !=
nullptr)
498 maybeDomainID.has_value(),
499 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
502 if (authorizedByDomain)
507 maybeDomainID.has_value() && sleToken ==
nullptr,
508 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
522 UNREACHABLE(
"xrpl::enforceMPTokenAuthorization : condition list is incomplete");
531 sleHolding.
getType() == ltRIPPLE_STATE || sleHolding.
getType() == ltMPTOKEN,
532 "xrpl::assetOfHolding",
533 "unexpected holding type");
535 sleShareIssuance.
getType() == ltMPTOKEN_ISSUANCE,
536 "xrpl::assetOfHolding",
537 "not SLE MPTokenIssuance");
539 if (sleHolding.
getType() == ltMPTOKEN)
542 auto const vaultPseudo = sleShareIssuance.
at(sfIssuer);
545 auto const& iouIssuer =
546 (lowLimit.getIssuer() != vaultPseudo) ? lowLimit.
getIssuer() : highLimit.getIssuer();
547 return Issue{lowLimit.get<
Issue>().currency, iouIssuer};
560 auto const sleIssuance = view.
read(mptID);
564 auto const issuer = (*sleIssuance)[sfIssuer];
568 if (!sleIssuance->isFlag(lsfMPTCanTransfer))
578 if (view.
rules().
enabled(fixCleanup3_2_0) && sleIssuance->isFieldPresent(sfReferenceHolding))
585 UNREACHABLE(
"xrpl::MPTokenHelpers::canTransfer : reached asset check depth");
590 auto const sleHolding =
616 if (!sleIssuance->isFlag(lsfMPTCanTrade))
623 sleIssuance->isFieldPresent(sfReferenceHolding))
631 UNREACHABLE(
"xrpl::MPTokenHelpers::canTrade : reached asset check depth");
635 auto const sleHolding =
668 auto sleIssuance = view.
peek(mptID);
671 JLOG(j.
error()) <<
"lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
677 JLOG(j.
error()) <<
"lockEscrowMPT: sender is the issuer, cannot lock MPTs.";
685 auto sle = view.
peek(mptokenID);
688 JLOG(j.
error()) <<
"lockEscrowMPT: MPToken not found for " << sender;
693 auto const pay = amount.
mpt().
value();
698 JLOG(j.
error()) <<
"lockEscrowMPT: insufficient MPTAmount for " <<
to_string(sender)
699 <<
": " << amt <<
" < " << pay;
703 (*sle)[sfMPTAmount] = amt - pay;
706 uint64_t
const locked = (*sle)[~sfLockedAmount].valueOr(0);
710 JLOG(j.
error()) <<
"lockEscrowMPT: overflow on locked amount for " <<
to_string(sender)
711 <<
": " << locked <<
" + " << pay;
715 if (sle->isFieldPresent(sfLockedAmount))
717 (*sle)[sfLockedAmount] += pay;
721 sle->setFieldU64(sfLockedAmount, pay);
730 uint64_t
const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].valueOr(0);
731 auto const pay = amount.
mpt().
value();
736 JLOG(j.
error()) <<
"lockEscrowMPT: overflow on issuance "
738 << mptIssue.getMptID() <<
": " << issuanceEscrowed <<
" + " << pay;
742 if (sleIssuance->isFieldPresent(sfLockedAmount))
744 (*sleIssuance)[sfLockedAmount] += pay;
748 sleIssuance->setFieldU64(sfLockedAmount, pay);
767 netAmount == grossAmount,
768 "xrpl::unlockEscrowMPT : netAmount == grossAmount");
770 auto const& issuer = netAmount.
getIssuer();
773 auto sleIssuance = view.
peek(mptID);
776 JLOG(j.
error()) <<
"unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
782 if (!sleIssuance->isFieldPresent(sfLockedAmount))
784 JLOG(j.
error()) <<
"unlockEscrowMPT: no locked amount in issuance for "
785 << mptIssue.getMptID();
789 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
790 auto const redeem = grossAmount.
mpt().
value();
795 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient locked amount for "
796 << mptIssue.getMptID() <<
": " << locked <<
" < " << redeem;
800 auto const newLocked = locked - redeem;
803 sleIssuance->makeFieldAbsent(sfLockedAmount);
807 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
812 if (issuer != receiver)
816 auto sle = view.
peek(mptokenID);
819 JLOG(j.
error()) <<
"unlockEscrowMPT: MPToken not found for " << receiver;
824 auto delta = netAmount.
mpt().
value();
829 JLOG(j.
error()) <<
"unlockEscrowMPT: overflow on MPTAmount for " <<
to_string(receiver)
830 <<
": " << current <<
" + " << delta;
834 (*sle)[sfMPTAmount] += delta;
840 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
841 auto const redeem = netAmount.
mpt().
value();
846 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient outstanding amount for "
847 << mptIssue.getMptID() <<
": " << outstanding <<
" < " << redeem;
851 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
855 if (issuer == sender)
857 JLOG(j.
error()) <<
"unlockEscrowMPT: sender is the issuer, "
858 "cannot unlock MPTs.";
863 auto sle = view.
peek(mptokenID);
866 JLOG(j.
error()) <<
"unlockEscrowMPT: MPToken not found for " << sender;
870 if (!sle->isFieldPresent(sfLockedAmount))
872 JLOG(j.
error()) <<
"unlockEscrowMPT: no locked amount in MPToken for " <<
to_string(sender);
876 auto const locked = sle->
getFieldU64(sfLockedAmount);
877 auto const delta = grossAmount.
mpt().
value();
882 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient locked amount for " <<
to_string(sender)
883 <<
": " << locked <<
" < " << delta;
887 auto const newLocked = locked - delta;
890 sle->makeFieldAbsent(sfLockedAmount);
894 sle->setFieldU64(sfLockedAmount, newLocked);
905 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
909 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient outstanding amount for "
910 << mptIssue.getMptID() <<
": " << outstanding <<
" < " << diff;
914 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
923 MPTID const& mptIssuanceID,
930 auto const ownerNode =
937 (*mptoken)[sfAccount] = account;
938 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
939 (*mptoken)[sfFlags] = flags;
940 (*mptoken)[sfOwnerNode] = *ownerNode;
963 if (!view.
exists(mptokenID))
1001 auto const outstanding = sleIssuance[sfOutstandingAmount];
1002 return max - outstanding;
1024 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.
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 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.
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
std::shared_ptr< STLedgerEntry > const & ref
LedgerEntryType getType() const
uint192 getFieldH192(SField const &field) const
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
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 unchecked(uint256 const &key) noexcept
Any ledger entry.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
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)
TER createMPToken(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, SLE::ref sponsorSle, std::uint32_t const flags)
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 checkCreateMPT(xrpl::ApplyView &view, xrpl::MPTIssue const &mptIssue, xrpl::AccountID const &holder, SLE::ref sponsorSle, std::uint32_t flags, beast::Journal j)
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.
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).
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
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 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)
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.
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)
std::expected< SLE::pointer, TER > getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle)
The transaction's reserve sponsor for the given account, if applicable.
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
void addSponsorToLedgerEntry(SLE::ref sle, SLE::const_ref sponsorSle, SF_ACCOUNT const &field=sfSponsor)
Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE.
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.
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.
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)
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
WaiveMPTCanTransfer
Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs.
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
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.
TER checkReserve(ApplyViewContext ctx, SLE::const_ref accSle, XRPAmount accBalance, SLE::const_ref sponsorSle, Adjustment adj, beast::Journal j, TER insufReserveCode=tecINSUFFICIENT_RESERVE)
Check if an account has sufficient reserve.
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.
bool isPseudoAccount(SLE::const_pointer sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
std::uint32_t ownerCount(SLE::const_ref 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").
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.