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/Zero.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/ledger/ApplyView.h>
9#include <xrpl/ledger/ReadView.h>
10#include <xrpl/ledger/View.h>
11#include <xrpl/ledger/helpers/AccountRootHelpers.h>
12#include <xrpl/ledger/helpers/CredentialHelpers.h>
13#include <xrpl/ledger/helpers/DirectoryHelpers.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>
44 return sle->isFlag(lsfMPTLocked);
52 return sle->isFlag(lsfMPTLocked);
77 for (
auto const& account : accounts)
83 for (
auto const& account : accounts)
99 sle && sle->isFieldPresent(sfTransferFee))
101 auto const fee = sle->getFieldU16(sfTransferFee);
102 XRPL_ASSERT(fee <=
kMaxTransferFee,
"xrpl::transferRate : fee is too large");
103 return Rate{1'000'000'000u + (10'000 * fee)};
118 if (!issuance->isFlag(lsfMPTCanTransfer))
134 auto const& mptID = mptIssue.
getMptID();
138 if (mpt->isFlag(lsfMPTLocked))
152 MPTID const& mptIssuanceID,
170 if ((flags & tfMPTUnauthorize) != 0u)
173 auto const sleMpt = view.
peek(mptokenKey);
174 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
176 (*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
198 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
200 (uOwnerCount < 2) ?
XRPAmount(beast::kZero)
203 if (priorBalance < reserveCreate)
208 if (!mpt || mpt->getAccountID(sfIssuer) == account)
211 UNREACHABLE(
"xrpl::authorizeMPToken : invalid issuance or issuers token");
219 if (
auto ter =
dirLink(view, account, mptoken))
222 (*mptoken)[sfAccount] = account;
223 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
224 (*mptoken)[sfFlags] = 0;
240 if (account != (*sleMptIssuance)[sfIssuer])
252 if ((flags & tfMPTUnauthorize) != 0u)
254 flagsOut &= ~lsfMPTAuthorized;
260 flagsOut |= lsfMPTAuthorized;
263 if (flagsIn != flagsOut)
264 sleMpt->setFieldU32(sfFlags, flagsOut);
280 bool const accountIsIssuer = accountID == mptIssue.
getIssuer();
281 auto const& mptID = mptIssue.
getMptID();
289 if (mptoken->at(sfMPTAmount) != 0 ||
290 (view.
rules().
enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
294 if (mptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
295 mptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
296 mptoken->isFieldPresent(sfIssuerEncryptedBalance) ||
297 mptoken->isFieldPresent(sfAuditorEncryptedBalance))
320 bool const fix330Enabled = view.
rules().
enabled(fixCleanup3_3_0);
321 bool const featureSAVEnabled = view.
rules().
enabled(featureSingleAssetVault);
322 bool const featureMPTV2Enabled = view.
rules().
enabled(featureMPTokensV2);
327 auto const isPseudoAccountExempt = [&] {
328 return (featureSAVEnabled || featureMPTV2Enabled) &&
329 isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID, &sfAMMID});
333 auto const sleIssuance = view.
read(mptID);
337 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
340 if (mptIssuer == account)
344 if (fix330Enabled && isPseudoAccountExempt())
347 if (featureSAVEnabled)
352 UNREACHABLE(
"xrpl::MPTokenHelpers::requireAuth : reached asset check depth");
362 if (sleIssuer->isFieldPresent(sfVaultID))
364 auto const sleVault = view.
read(
keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
368 auto const asset = sleVault->at(sfAsset);
369 if (
auto const err = asset.visit(
370 [&](
Issue const& issue) { return requireAuth(view, issue, account, authType); },
372 return requireAuth(view, issue, account, authType, depth + 1);
380 auto const sleToken = view.
read(mptokenID);
388 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
392 sleIssuance->isFlag(lsfMPTRequireAuth),
393 "xrpl::requireAuth : issuance requires authorization");
409 if (!fix330Enabled && isPseudoAccountExempt())
413 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
414 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
423 MPTID const& mptIssuanceID,
433 sleIssuance->isFlag(lsfMPTRequireAuth),
434 "xrpl::enforceMPTokenAuthorization : authorization required");
436 if (account == sleIssuance->at(sfIssuer))
441 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
442 bool expired =
false;
443 bool const authorizedByDomain = [&]() ->
bool {
445 if (!maybeDomainID.has_value())
456 if (!authorizedByDomain && sleToken ==
nullptr)
467 if (!authorizedByDomain && maybeDomainID.has_value())
474 if (!authorizedByDomain)
479 sleToken !=
nullptr && !maybeDomainID.has_value(),
480 "xrpl::enforceMPTokenAuthorization : found MPToken");
481 if (sleToken->isFlag(lsfMPTAuthorized))
486 if (authorizedByDomain && sleToken !=
nullptr)
491 maybeDomainID.has_value(),
492 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
495 if (authorizedByDomain)
500 maybeDomainID.has_value() && sleToken ==
nullptr,
501 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
515 UNREACHABLE(
"xrpl::enforceMPTokenAuthorization : condition list is incomplete");
524 sleHolding.
getType() == ltRIPPLE_STATE || sleHolding.
getType() == ltMPTOKEN,
525 "xrpl::assetOfHolding",
526 "unexpected holding type");
528 sleShareIssuance.
getType() == ltMPTOKEN_ISSUANCE,
529 "xrpl::assetOfHolding",
530 "not SLE MPTokenIssuance");
532 if (sleHolding.
getType() == ltMPTOKEN)
535 auto const vaultPseudo = sleShareIssuance.
at(sfIssuer);
538 auto const& iouIssuer =
539 (lowLimit.getIssuer() != vaultPseudo) ? lowLimit.
getIssuer() : highLimit.getIssuer();
540 return Issue{lowLimit.get<
Issue>().currency, iouIssuer};
553 auto const sleIssuance = view.
read(mptID);
557 auto const issuer = (*sleIssuance)[sfIssuer];
561 if (!sleIssuance->isFlag(lsfMPTCanTransfer))
571 if (view.
rules().
enabled(fixCleanup3_2_0) && sleIssuance->isFieldPresent(sfReferenceHolding))
578 UNREACHABLE(
"xrpl::MPTokenHelpers::canTransfer : reached asset check depth");
583 auto const sleHolding =
609 if (!sleIssuance->isFlag(lsfMPTCanTrade))
616 sleIssuance->isFieldPresent(sfReferenceHolding))
624 UNREACHABLE(
"xrpl::MPTokenHelpers::canTrade : reached asset check depth");
628 auto const sleHolding =
661 auto sleIssuance = view.
peek(mptID);
664 JLOG(j.
error()) <<
"lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
670 JLOG(j.
error()) <<
"lockEscrowMPT: sender is the issuer, cannot lock MPTs.";
678 auto sle = view.
peek(mptokenID);
681 JLOG(j.
error()) <<
"lockEscrowMPT: MPToken not found for " << sender;
686 auto const pay = amount.
mpt().
value();
691 JLOG(j.
error()) <<
"lockEscrowMPT: insufficient MPTAmount for " <<
to_string(sender)
692 <<
": " << amt <<
" < " << pay;
696 (*sle)[sfMPTAmount] = amt - pay;
699 uint64_t
const locked = (*sle)[~sfLockedAmount].valueOr(0);
703 JLOG(j.
error()) <<
"lockEscrowMPT: overflow on locked amount for " <<
to_string(sender)
704 <<
": " << locked <<
" + " << pay;
708 if (sle->isFieldPresent(sfLockedAmount))
710 (*sle)[sfLockedAmount] += pay;
714 sle->setFieldU64(sfLockedAmount, pay);
723 uint64_t
const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].valueOr(0);
724 auto const pay = amount.
mpt().
value();
729 JLOG(j.
error()) <<
"lockEscrowMPT: overflow on issuance "
731 << mptIssue.getMptID() <<
": " << issuanceEscrowed <<
" + " << pay;
735 if (sleIssuance->isFieldPresent(sfLockedAmount))
737 (*sleIssuance)[sfLockedAmount] += pay;
741 sleIssuance->setFieldU64(sfLockedAmount, pay);
760 netAmount == grossAmount,
761 "xrpl::unlockEscrowMPT : netAmount == grossAmount");
763 auto const& issuer = netAmount.
getIssuer();
766 auto sleIssuance = view.
peek(mptID);
769 JLOG(j.
error()) <<
"unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
775 if (!sleIssuance->isFieldPresent(sfLockedAmount))
777 JLOG(j.
error()) <<
"unlockEscrowMPT: no locked amount in issuance for "
778 << mptIssue.getMptID();
782 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
783 auto const redeem = grossAmount.
mpt().
value();
788 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient locked amount for "
789 << mptIssue.getMptID() <<
": " << locked <<
" < " << redeem;
793 auto const newLocked = locked - redeem;
796 sleIssuance->makeFieldAbsent(sfLockedAmount);
800 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
805 if (issuer != receiver)
809 auto sle = view.
peek(mptokenID);
812 JLOG(j.
error()) <<
"unlockEscrowMPT: MPToken not found for " << receiver;
817 auto delta = netAmount.
mpt().
value();
822 JLOG(j.
error()) <<
"unlockEscrowMPT: overflow on MPTAmount for " <<
to_string(receiver)
823 <<
": " << current <<
" + " << delta;
827 (*sle)[sfMPTAmount] += delta;
833 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
834 auto const redeem = netAmount.
mpt().
value();
839 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient outstanding amount for "
840 << mptIssue.getMptID() <<
": " << outstanding <<
" < " << redeem;
844 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
848 if (issuer == sender)
850 JLOG(j.
error()) <<
"unlockEscrowMPT: sender is the issuer, "
851 "cannot unlock MPTs.";
856 auto sle = view.
peek(mptokenID);
859 JLOG(j.
error()) <<
"unlockEscrowMPT: MPToken not found for " << sender;
863 if (!sle->isFieldPresent(sfLockedAmount))
865 JLOG(j.
error()) <<
"unlockEscrowMPT: no locked amount in MPToken for " <<
to_string(sender);
869 auto const locked = sle->
getFieldU64(sfLockedAmount);
870 auto const delta = grossAmount.
mpt().
value();
875 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient locked amount for " <<
to_string(sender)
876 <<
": " << locked <<
" < " << delta;
880 auto const newLocked = locked - delta;
883 sle->makeFieldAbsent(sfLockedAmount);
887 sle->setFieldU64(sfLockedAmount, newLocked);
898 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
902 JLOG(j.
error()) <<
"unlockEscrowMPT: insufficient outstanding amount for "
903 << mptIssue.getMptID() <<
": " << outstanding <<
" < " << diff;
907 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
916 MPTID const& mptIssuanceID,
922 auto const ownerNode =
929 (*mptoken)[sfAccount] = account;
930 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
931 (*mptoken)[sfFlags] = flags;
932 (*mptoken)[sfOwnerNode] = *ownerNode;
951 if (!view.
exists(mptokenID))
978 auto const outstanding = sleIssuance[sfOutstandingAmount];
979 return max - outstanding;
1001 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 Fees const & fees() const =0
Returns the fees for the base ledger.
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
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 mptokenIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::int64_t maxMPTAmount(SLE const &sleIssuance)
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
TER checkCreateMPT(xrpl::ApplyView &view, xrpl::MPTIssue const &mptIssue, xrpl::AccountID const &holder, beast::Journal j)
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
AllowMPTOverflow
Controls whether accountSend is allowed to overflow OutstandingAmount.
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
TER lockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
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 authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
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)
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.
TER createMPToken(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, std::uint32_t const flags)
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)
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
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.
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 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.
@ tecINSUFFICIENT_RESERVE
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
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...
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, MPTIssue const &mptIssue, beast::Journal journal)
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)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Represents a transfer rate.