xrpld
Loading...
Searching...
No Matches
MPTokenHelpers.cpp
1#include <xrpl/ledger/helpers/MPTokenHelpers.h>
2
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>
30
31#include <algorithm>
32#include <cstdint>
33#include <initializer_list>
34#include <limits>
35#include <memory>
36#include <optional>
37#include <stdexcept>
38
39namespace xrpl {
40
41bool
42isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
43{
44 if (auto const sle = view.read(keylet::mptokenIssuance(mptIssue.getMptID())))
45 return isGlobalFrozen(*sle);
46 return false;
47}
48
49bool
50isGlobalFrozen(SLE const& issuanceSle)
51{
52 XRPL_ASSERT(
53 issuanceSle.getType() == ltMPTOKEN_ISSUANCE, "xrpl::isGlobalFrozen : MPTokenIssuance SLE");
54
55 return issuanceSle.isFlag(lsfMPTLocked);
56}
57
58bool
59isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
60{
61 if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
62 return isIndividualFrozen(*sle);
63 return false;
64}
65
66bool
67isIndividualFrozen(SLE const& mptSle)
68{
69 XRPL_ASSERT(mptSle.getType() == ltMPTOKEN, "xrpl::isIndividualFrozen : MPToken SLE");
70
71 return mptSle.isFlag(lsfMPTLocked);
72}
73
74bool
76 ReadView const& view,
77 AccountID const& account,
78 MPTIssue const& mptIssue,
79 std::uint8_t depth)
80{
81 return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
82 isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
83}
84
85bool
86isFrozen(ReadView const& view, AccountID const& account, SLE const& sle, std::uint8_t depth)
87{
88 XRPL_ASSERT(
89 sle.getType() == ltMPTOKEN || sle.getType() == ltMPTOKEN_ISSUANCE,
90 "xrpl::isFrozen : MPToken or MPTokenIssuance SLE");
91
92 if (sle.getType() == ltMPTOKEN)
93 {
94 XRPL_ASSERT(sle[sfAccount] == account, "xrpl::isFrozen : valid MPToken holder");
95
96 MPTID const mptID = sle[sfMPTokenIssuanceID];
97 auto const issuanceSle = view.read(keylet::mptokenIssuance(mptID));
98
99 if ((issuanceSle && isGlobalFrozen(*issuanceSle)) || isIndividualFrozen(sle))
100 return true;
101
102 if (issuanceSle)
103 return isVaultPseudoAccountFrozen(view, account, *issuanceSle, depth);
104
105 return isVaultPseudoAccountFrozen(view, account, MPTIssue{mptID}, depth);
106 }
107
108 MPTIssue const mptIssue{sle[sfSequence], sle[sfIssuer]};
109 return isGlobalFrozen(sle) || isIndividualFrozen(view, account, mptIssue) ||
110 isVaultPseudoAccountFrozen(view, account, sle, depth);
111}
112
113[[nodiscard]] bool
115 ReadView const& view,
116 std::initializer_list<AccountID> const& accounts,
117 MPTIssue const& mptIssue,
118 std::uint8_t depth)
119{
120 auto const issuanceSle = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
121 if (issuanceSle && isGlobalFrozen(*issuanceSle))
122 return true;
123
124 for (auto const& account : accounts)
125 {
126 if (isIndividualFrozen(view, account, mptIssue))
127 return true;
128 }
129
130 // Pass the issuance SLE when we have it to avoid re-reading it per account;
131 // otherwise defer to the MPTIssue overload, which handles a missing issuance.
132 auto const anyVaultFrozen = [&](auto const& shareOrIssuance) {
133 return std::ranges::any_of(accounts, [&](auto const& account) {
134 return isVaultPseudoAccountFrozen(view, account, shareOrIssuance, depth);
135 });
136 };
137
138 return issuanceSle ? anyVaultFrozen(*issuanceSle) : anyVaultFrozen(mptIssue);
139}
140
141Rate
142transferRate(ReadView const& view, MPTID const& issuanceID)
143{
144 // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
145 // For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
146 // which represents 50% of 1,000,000,000
147 if (auto const sle = view.read(keylet::mptokenIssuance(issuanceID));
148 sle && sle->isFieldPresent(sfTransferFee))
149 {
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)};
153 }
154
155 return kParityRate;
156}
157
158[[nodiscard]] TER
159canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
160{
161 auto mptID = mptIssue.getMptID();
162 auto issuance = view.read(keylet::mptokenIssuance(mptID));
163 if (!issuance)
164 {
165 return tecOBJECT_NOT_FOUND;
166 }
167 if (!issuance->isFlag(lsfMPTCanTransfer))
168 {
169 return tecNO_AUTH;
170 }
171
172 return tesSUCCESS;
173}
174
175[[nodiscard]] TER
178 AccountID const& accountID,
179 XRPAmount priorBalance,
180 MPTIssue const& mptIssue,
181 beast::Journal journal)
182{
183 auto const& mptID = mptIssue.getMptID();
184 auto const mpt = ctx.view.peek(keylet::mptokenIssuance(mptID));
185 if (!mpt)
186 return tefINTERNAL; // LCOV_EXCL_LINE
187 // Unlike IOU addEmptyHolding (post-fixCleanup3_4_0), a locked issuance is
188 // still rejected before the "MPToken already exists" short circuit.
189 if (mpt->isFlag(lsfMPTLocked))
190 return tefINTERNAL; // LCOV_EXCL_LINE
191 if (ctx.view.peek(keylet::mptoken(mptID, accountID)))
192 return tecDUPLICATE;
193 if (accountID == mptIssue.getIssuer())
194 return tesSUCCESS;
195
196 return authorizeMPToken(ctx, priorBalance, mptID, accountID, journal);
197}
198
199[[nodiscard]] TER
202 XRPAmount const& priorBalance,
203 MPTID const& mptIssuanceID,
204 AccountID const& account,
205 beast::Journal journal,
206 std::uint32_t flags,
208{
209 auto const sleAcct = ctx.view.peek(keylet::account(account));
210 if (!sleAcct)
211 return tecINTERNAL; // LCOV_EXCL_LINE
212
213 // If the account that submitted the tx is a holder
214 // Note: `account_` is holder's account
215 // `holderID` is NOT used
216 if (!holderID)
217 {
218 // When a holder wants to unauthorize/delete a MPT, the ledger must
219 // - delete mptokenKey from owner directory
220 // - delete the MPToken
221 if ((flags & tfMPTUnauthorize) != 0u)
222 {
223 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
224 auto const sleMpt = ctx.view.peek(mptokenKey);
225 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
226 (ctx.view.rules().enabled(fixCleanup3_1_3) &&
227 (*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
228 return tecINTERNAL; // LCOV_EXCL_LINE
229
230 if (!ctx.view.dirRemove(
231 keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
232 return tecINTERNAL; // LCOV_EXCL_LINE
233
234 decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt, 1, journal);
235
236 ctx.view.erase(sleMpt);
237 return tesSUCCESS;
238 }
239
240 // A potential holder wants to authorize/hold a mpt, the ledger must:
241 // - add the new mptokenKey to the owner directory
242 // - create the MPToken object for the holder
243
244 // A reserve sponsor only covers tx.Account's own objects.
245 auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleAcct);
246 if (!sponsorExp)
247 return sponsorExp.error(); // LCOV_EXCL_LINE
248 auto const sponsorSle = *sponsorExp;
249
250 // The reserve that is required to create the MPToken. Note
251 // that although the reserve increases with every item
252 // an account owns, in the case of MPTokens we only
253 // *enforce* a reserve if the user owns more than two
254 // items. This is similar to the reserve requirements of trust lines.
255 // The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on
256 // the tx — the sponsor must always cover the reserve (via balance or prefunded
257 // budget), so this check always runs for sponsored transactions.
258 if (sponsorSle || ownerCount(sleAcct, journal) >= 2)
259 {
260 if (auto const ret = checkReserve(
261 ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
262 !isTesSuccess(ret))
263 return ret;
264 }
265
266 // Defensive check before we attempt to create MPToken for the issuer
267 auto const mpt = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
268 if (!mpt || mpt->getAccountID(sfIssuer) == account)
269 {
270 // LCOV_EXCL_START
271 UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
272 if (ctx.view.rules().enabled(featureLendingProtocol))
273 return tecINTERNAL;
274 // LCOV_EXCL_STOP
275 }
276
277 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
278 auto mptoken = std::make_shared<SLE>(mptokenKey);
279 if (auto ter = dirLink(ctx.view, account, mptoken))
280 return ter; // LCOV_EXCL_LINE
281
282 (*mptoken)[sfAccount] = account;
283 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
284 (*mptoken)[sfFlags] = 0;
285 ctx.view.insert(mptoken);
286
287 // Update owner count.
288 increaseOwnerCount(ctx.view, sleAcct, sponsorSle, 1, journal);
289 addSponsorToLedgerEntry(mptoken, sponsorSle);
290
291 return tesSUCCESS;
292 }
293
294 auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
295 if (!sleMptIssuance)
296 return tecINTERNAL; // LCOV_EXCL_LINE
297
298 // If the account that submitted this tx is the issuer of the MPT
299 // Note: `account_` is issuer's account
300 // `holderID` is holder's account
301 if (account != (*sleMptIssuance)[sfIssuer])
302 return tecINTERNAL; // LCOV_EXCL_LINE
303
304 auto const sleMpt = ctx.view.peek(keylet::mptoken(mptIssuanceID, *holderID));
305 if (!sleMpt)
306 return tecINTERNAL; // LCOV_EXCL_LINE
307
308 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
309 std::uint32_t flagsOut = flagsIn;
310
311 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
312 // their MPToken
313 if ((flags & tfMPTUnauthorize) != 0u)
314 {
315 flagsOut &= ~lsfMPTAuthorized;
316 }
317 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
318 // MPToken
319 else
320 {
321 flagsOut |= lsfMPTAuthorized;
322 }
323
324 if (flagsIn != flagsOut)
325 sleMpt->setFieldU32(sfFlags, flagsOut);
326
327 ctx.view.update(sleMpt);
328 return tesSUCCESS;
329}
330
331[[nodiscard]] TER
334 AccountID const& accountID,
335 MPTIssue const& mptIssue,
336 beast::Journal journal)
337{
338 // If the account is the issuer, then no token should exist. MPTs do not
339 // have the legacy ability to create such a situation, but check anyway. If
340 // a token does exist, it will get deleted. If not, return success.
341 bool const accountIsIssuer = accountID == mptIssue.getIssuer();
342 auto const& mptID = mptIssue.getMptID();
343 auto const mptoken = ctx.view.peek(keylet::mptoken(mptID, accountID));
344 if (!mptoken)
345 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
346 // Unlike a trust line, if the account is the issuer, and the token has a
347 // balance, it can not just be deleted, because that will throw the issuance
348 // accounting out of balance, so fail. Since this should be impossible
349 // anyway, I'm not going to put any effort into it.
350 if (mptoken->at(sfMPTAmount) != 0 ||
351 (ctx.view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
352 return tecHAS_OBLIGATIONS;
353
354 // Don't delete if the token still has confidential balances
355 if (mptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
356 mptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
357 mptoken->isFieldPresent(sfIssuerEncryptedBalance) ||
358 mptoken->isFieldPresent(sfAuditorEncryptedBalance))
359 {
360 return tecHAS_OBLIGATIONS;
361 }
362
363 return authorizeMPToken(
364 ctx,
365 {}, // priorBalance
366 mptID,
367 accountID,
368 journal,
369 tfMPTUnauthorize // flags
370 );
371}
372
373[[nodiscard]] TER
375 ReadView const& view,
376 MPTIssue const& mptIssue,
377 AccountID const& account,
378 AuthType authType,
379 std::uint8_t depth)
380{
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);
384
385 // Pseudo-accounts (Vault, LoanBroker, AMM) hold assets on behalf of their participants.
386 // They are implicitly authorized for any MPT they hold, including vault shares whose
387 // underlying asset would otherwise require auth.
388 auto const isPseudoAccountExempt = [&] {
389 return (featureSAVEnabled || featureMPTV2Enabled) && isPseudoAccount(view, account);
390 };
391
392 auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
393 auto const sleIssuance = view.read(mptID);
394 if (!sleIssuance)
395 return tecOBJECT_NOT_FOUND;
396
397 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
398
399 // issuer is always "authorized"
400 if (mptIssuer == account) // Issuer won't have MPToken
401 return tesSUCCESS;
402
403 // Post-fix330: exempt before the recursive underlying-asset auth check.
404 if (fix330Enabled && isPseudoAccountExempt())
405 return tesSUCCESS;
406
407 if (featureSAVEnabled)
408 {
409 if (depth >= kMaxAssetCheckDepth)
410 {
411 // LCOV_EXCL_START
412 UNREACHABLE("xrpl::MPTokenHelpers::requireAuth : reached asset check depth");
413 return tecINTERNAL;
414 // LCOV_EXCL_STOP
415 }
416
417 // requireAuth is recursive if the issuer is a vault pseudo-account
418 auto const sleIssuer = view.read(keylet::account(mptIssuer));
419 if (!sleIssuer)
420 return tefINTERNAL; // LCOV_EXCL_LINE
421
422 if (sleIssuer->isFieldPresent(sfVaultID))
423 {
424 auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
425 if (!sleVault)
426 return tefINTERNAL; // LCOV_EXCL_LINE
427
428 auto const asset = sleVault->at(sfAsset);
429 if (auto const err = asset.visit(
430 [&](Issue const& issue) { return requireAuth(view, issue, account, authType); },
431 [&](MPTIssue const& issue) {
432 return requireAuth(view, issue, account, authType, depth + 1);
433 });
434 !isTesSuccess(err))
435 return err;
436 }
437 }
438
439 auto const mptokenID = keylet::mptoken(mptID.key, account);
440 auto const sleToken = view.read(mptokenID);
441
442 // if account has no MPToken, fail
443 if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
444 return tecNO_AUTH;
445
446 // Note, this check is not amendment-gated because DomainID will be always
447 // empty **unless** writing to it has been enabled by an amendment
448 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
449 if (maybeDomainID)
450 {
451 XRPL_ASSERT(
452 sleIssuance->isFlag(lsfMPTRequireAuth),
453 "xrpl::requireAuth : issuance requires authorization");
454 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
455 auto const ter = credentials::validDomain(view, *maybeDomainID, account);
456 if (isTesSuccess(ter))
457 {
458 return ter; // Note: sleToken might be null
459 }
460 if (!sleToken)
461 {
462 return ter;
463 }
464 // We ignore error from validDomain if we found sleToken, as it could
465 // belong to someone who is explicitly authorized e.g. a vault owner.
466 }
467
468 // Pre-fix330: exempt after domain/sleToken checks, preserving prior behavior.
469 if (!fix330Enabled && isPseudoAccountExempt())
470 return tesSUCCESS;
471
472 // mptoken must be authorized if issuance enabled requireAuth
473 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
474 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
475 return tecNO_AUTH;
476
477 return tesSUCCESS; // Note: sleToken might be null
478}
479
480[[nodiscard]] TER
483 MPTID const& mptIssuanceID,
484 AccountID const& account,
485 XRPAmount const& priorBalance, // for MPToken authorization
487{
488 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
489 if (!sleIssuance)
490 return tefINTERNAL; // LCOV_EXCL_LINE
491
492 XRPL_ASSERT(
493 sleIssuance->isFlag(lsfMPTRequireAuth),
494 "xrpl::enforceMPTokenAuthorization : authorization required");
495
496 if (account == sleIssuance->at(sfIssuer))
497 return tefINTERNAL; // LCOV_EXCL_LINE
498
499 auto const keylet = keylet::mptoken(mptIssuanceID, account);
500 auto const sleToken = ctx.view.read(keylet); // NOTE: might be null
501 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
502 bool expired = false;
503 bool const authorizedByDomain = [&]() -> bool {
504 // NOTE: defensive here, should be checked in preclaim
505 if (!maybeDomainID.has_value())
506 return false; // LCOV_EXCL_LINE
507
508 auto const ter = verifyValidDomain(ctx.view, account, *maybeDomainID, j);
509 if (isTesSuccess(ter))
510 return true;
511 if (ter == tecEXPIRED)
512 expired = true;
513 return false;
514 }();
515
516 if (!authorizedByDomain && sleToken == nullptr)
517 {
518 // Could not find MPToken and won't create one, could be either of:
519 //
520 // 1. Field sfDomainID not set in MPTokenIssuance or
521 // 2. Account has no matching and accepted credentials or
522 // 3. Account has all expired credentials (deleted in verifyValidDomain)
523 //
524 // Either way, return tecNO_AUTH and there is nothing else to do
525 return expired ? tecEXPIRED : tecNO_AUTH;
526 }
527 if (!authorizedByDomain && maybeDomainID.has_value())
528 {
529 // Found an MPToken but the account is not authorized and we expect
530 // it to have been authorized by the domain. This could be because the
531 // credentials used to create the MPToken have expired or been deleted.
532 return expired ? tecEXPIRED : tecNO_AUTH;
533 }
534 if (!authorizedByDomain)
535 {
536 // We found an MPToken, but sfDomainID is not set, so this is a classic
537 // MPToken which requires authorization by the token issuer.
538 XRPL_ASSERT(
539 sleToken != nullptr && !maybeDomainID.has_value(),
540 "xrpl::enforceMPTokenAuthorization : found MPToken");
541 if (sleToken->isFlag(lsfMPTAuthorized))
542 return tesSUCCESS;
543
544 return tecNO_AUTH;
545 }
546 if (authorizedByDomain && sleToken != nullptr)
547 {
548 // Found an MPToken, authorized by the domain. Ignore authorization flag
549 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
550 XRPL_ASSERT(
551 maybeDomainID.has_value(),
552 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
553 return tesSUCCESS;
554 }
555 if (authorizedByDomain)
556 {
557 // Could not find MPToken but there should be one because we are
558 // authorized by domain. Proceed to create it, then return tesSUCCESS
559 XRPL_ASSERT(
560 maybeDomainID.has_value() && sleToken == nullptr,
561 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
562 if (auto const err = authorizeMPToken(
563 ctx,
564 priorBalance, // priorBalance
565 mptIssuanceID, // mptIssuanceID
566 account, // account
567 j);
568 !isTesSuccess(err))
569 return err;
570
571 return tesSUCCESS;
572 }
573
574 // LCOV_EXCL_START
575 UNREACHABLE("xrpl::enforceMPTokenAuthorization : condition list is incomplete");
576 return tefINTERNAL;
577 // LCOV_EXCL_STOP
578}
579
580[[nodiscard]] Asset
581assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding)
582{
583 XRPL_ASSERT_PARTS(
584 sleHolding.getType() == ltRIPPLE_STATE || sleHolding.getType() == ltMPTOKEN,
585 "xrpl::assetOfHolding",
586 "unexpected holding type");
587 XRPL_ASSERT_PARTS(
588 sleShareIssuance.getType() == ltMPTOKEN_ISSUANCE,
589 "xrpl::assetOfHolding",
590 "not SLE MPTokenIssuance");
591
592 if (sleHolding.getType() == ltMPTOKEN)
593 return MPTIssue{sleHolding.getFieldH192(sfMPTokenIssuanceID)};
594
595 auto const vaultPseudo = sleShareIssuance.at(sfIssuer);
596 auto const lowLimit = sleHolding.getFieldAmount(sfLowLimit);
597 auto const highLimit = sleHolding.getFieldAmount(sfHighLimit);
598 auto const& iouIssuer =
599 (lowLimit.getIssuer() != vaultPseudo) ? lowLimit.getIssuer() : highLimit.getIssuer();
600 return Issue{lowLimit.get<Issue>().currency, iouIssuer};
601}
602
603TER
605 ReadView const& view,
606 MPTIssue const& mptIssue,
607 AccountID const& from,
608 AccountID const& to,
610 std::uint8_t depth)
611{
612 auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
613 auto const sleIssuance = view.read(mptID);
614 if (!sleIssuance)
615 return tecOBJECT_NOT_FOUND;
616
617 auto const issuer = (*sleIssuance)[sfIssuer];
618 if (waive == WaiveMPTCanTransfer::Yes || from == issuer || to == issuer)
619 return tesSUCCESS;
620
621 if (!sleIssuance->isFlag(lsfMPTCanTransfer))
622 return TER{tecNO_AUTH};
623
624 // Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
625 // to the vault pseudo's MPToken or RippleState for the underlying asset.
626 // Third-party transfers inherit the underlying's transferability.
627 // Issuer-involving transfers and waived callers returned tesSUCCESS above.
628 //
629 // The recursive call always passes WaiveMPTCanTransfer::No so that
630 // a waived outer caller does not transitively unlock the underlying.
631 if (view.rules().enabled(fixCleanup3_2_0) && sleIssuance->isFieldPresent(sfReferenceHolding))
632 {
633 // Defensive depth bound on the inheritance recursion. Unreachable
634 // in practice (vault-of-vault-shares is forbidden at VaultCreate).
635 if (depth >= kMaxAssetCheckDepth)
636 {
637 // LCOV_EXCL_START
638 UNREACHABLE("xrpl::MPTokenHelpers::canTransfer : reached asset check depth");
639 return tecINTERNAL;
640 // LCOV_EXCL_STOP
641 }
642
643 auto const sleHolding =
644 view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
645 if (!sleHolding)
646 return tefINTERNAL; // LCOV_EXCL_LINE
647
648 return canTransfer(
649 view,
650 assetOfHolding(*sleIssuance, *sleHolding),
651 from,
652 to,
654 depth + 1);
655 }
656
657 return tesSUCCESS;
658}
659
660TER
661canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth)
662{
663 return asset.visit(
664 [&](Issue const&) -> TER { return tesSUCCESS; },
665 [&](MPTIssue const& mptIssue) -> TER {
666 auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
667 if (!sleIssuance)
668 return tecOBJECT_NOT_FOUND;
669 if (!sleIssuance->isFlag(lsfMPTCanTrade))
670 return tecNO_PERMISSION;
671
672 // Post-fixCleanup3_2_0: vault shares inherit the underlying
673 // asset's tradability. A share whose underlying has been
674 // removed from trading cannot itself be placed on the DEX.
675 if (view.rules().enabled(fixCleanup3_2_0) &&
676 sleIssuance->isFieldPresent(sfReferenceHolding))
677 {
678 // Defensive depth bound on the inheritance recursion.
679 // Unreachable in practice (vault-of-vault-shares
680 // forbidden at VaultCreate).
681 if (depth >= kMaxAssetCheckDepth)
682 {
683 // LCOV_EXCL_START
684 UNREACHABLE("xrpl::MPTokenHelpers::canTrade : reached asset check depth");
685 return tecINTERNAL;
686 // LCOV_EXCL_STOP
687 }
688 auto const sleHolding =
689 view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
690 if (!sleHolding)
691 return tefINTERNAL; // LCOV_EXCL_LINE
692
693 return canTrade(view, assetOfHolding(*sleIssuance, *sleHolding), depth + 1);
694 }
695
696 return tesSUCCESS;
697 });
698}
699
700TER
702 ReadView const& view,
703 Asset const& asset,
704 AccountID const& from,
705 AccountID const& to)
706{
707 if (!asset.holds<MPTIssue>())
708 return tesSUCCESS;
709
710 if (auto const ter = canTrade(view, asset); !isTesSuccess(ter))
711 return ter;
712
713 return canTransfer(view, asset, from, to);
714}
715
716TER
717lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
718{
719 auto const mptIssue = amount.get<MPTIssue>();
720 auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
721 auto sleIssuance = view.peek(mptID);
722 if (!sleIssuance)
723 { // LCOV_EXCL_START
724 JLOG(j.error()) << "lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
725 return tecOBJECT_NOT_FOUND;
726 } // LCOV_EXCL_STOP
727
728 if (amount.getIssuer() == sender)
729 { // LCOV_EXCL_START
730 JLOG(j.error()) << "lockEscrowMPT: sender is the issuer, cannot lock MPTs.";
731 return tecINTERNAL;
732 } // LCOV_EXCL_STOP
733
734 // 1. Decrease the MPT Holder MPTAmount
735 // 2. Increase the MPT Holder EscrowedAmount
736 {
737 auto const mptokenID = keylet::mptoken(mptID.key, sender);
738 auto sle = view.peek(mptokenID);
739 if (!sle)
740 { // LCOV_EXCL_START
741 JLOG(j.error()) << "lockEscrowMPT: MPToken not found for " << sender;
742 return tecOBJECT_NOT_FOUND;
743 } // LCOV_EXCL_STOP
744
745 auto const amt = sle->getFieldU64(sfMPTAmount);
746 auto const pay = amount.mpt().value();
747
748 // Underflow check for subtraction
749 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
750 { // LCOV_EXCL_START
751 JLOG(j.error()) << "lockEscrowMPT: insufficient MPTAmount for " << to_string(sender)
752 << ": " << amt << " < " << pay;
753 return tecINTERNAL;
754 } // LCOV_EXCL_STOP
755
756 (*sle)[sfMPTAmount] = amt - pay;
757
758 // Overflow check for addition
759 uint64_t const locked = (*sle)[~sfLockedAmount].valueOr(0);
760
761 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
762 { // LCOV_EXCL_START
763 JLOG(j.error()) << "lockEscrowMPT: overflow on locked amount for " << to_string(sender)
764 << ": " << locked << " + " << pay;
765 return tecINTERNAL;
766 } // LCOV_EXCL_STOP
767
768 if (sle->isFieldPresent(sfLockedAmount))
769 {
770 (*sle)[sfLockedAmount] += pay;
771 }
772 else
773 {
774 sle->setFieldU64(sfLockedAmount, pay);
775 }
776
777 view.update(sle);
778 }
779
780 // 1. Increase the Issuance EscrowedAmount
781 // 2. DO NOT change the Issuance OutstandingAmount
782 {
783 uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].valueOr(0);
784 auto const pay = amount.mpt().value();
785
786 // Overflow check for addition
787 if (!canAdd(STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
788 { // LCOV_EXCL_START
789 JLOG(j.error()) << "lockEscrowMPT: overflow on issuance "
790 "locked amount for "
791 << mptIssue.getMptID() << ": " << issuanceEscrowed << " + " << pay;
792 return tecINTERNAL;
793 } // LCOV_EXCL_STOP
794
795 if (sleIssuance->isFieldPresent(sfLockedAmount))
796 {
797 (*sleIssuance)[sfLockedAmount] += pay;
798 }
799 else
800 {
801 sleIssuance->setFieldU64(sfLockedAmount, pay);
802 }
803
804 view.update(sleIssuance);
805 }
806 return tesSUCCESS;
807}
808
809TER
811 ApplyView& view,
812 AccountID const& sender,
813 AccountID const& receiver,
814 STAmount const& netAmount,
815 STAmount const& grossAmount,
817{
818 XRPL_ASSERT_IF(
819 !view.rules().enabled(fixTokenEscrowV1),
820 netAmount == grossAmount,
821 "xrpl::unlockEscrowMPT : netAmount == grossAmount");
822
823 auto const& issuer = netAmount.getIssuer();
824 auto const& mptIssue = netAmount.get<MPTIssue>();
825 auto const mptID = keylet::mptokenIssuance(mptIssue.getMptID());
826 auto sleIssuance = view.peek(mptID);
827 if (!sleIssuance)
828 { // LCOV_EXCL_START
829 JLOG(j.error()) << "unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
830 return tecOBJECT_NOT_FOUND;
831 } // LCOV_EXCL_STOP
832
833 // Decrease the Issuance EscrowedAmount
834 {
835 if (!sleIssuance->isFieldPresent(sfLockedAmount))
836 { // LCOV_EXCL_START
837 JLOG(j.error()) << "unlockEscrowMPT: no locked amount in issuance for "
838 << mptIssue.getMptID();
839 return tecINTERNAL;
840 } // LCOV_EXCL_STOP
841
842 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
843 auto const redeem = grossAmount.mpt().value();
844
845 // Underflow check for subtraction
846 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
847 { // LCOV_EXCL_START
848 JLOG(j.error()) << "unlockEscrowMPT: insufficient locked amount for "
849 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
850 return tecINTERNAL;
851 } // LCOV_EXCL_STOP
852
853 auto const newLocked = locked - redeem;
854 if (newLocked == 0)
855 {
856 sleIssuance->makeFieldAbsent(sfLockedAmount);
857 }
858 else
859 {
860 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
861 }
862 view.update(sleIssuance);
863 }
864
865 if (issuer != receiver)
866 {
867 // Increase the MPT Holder MPTAmount
868 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
869 auto sle = view.peek(mptokenID);
870 if (!sle)
871 { // LCOV_EXCL_START
872 JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << receiver;
873 return tecOBJECT_NOT_FOUND;
874 } // LCOV_EXCL_STOP
875
876 auto current = sle->getFieldU64(sfMPTAmount);
877 auto delta = netAmount.mpt().value();
878
879 // Overflow check for addition
880 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
881 { // LCOV_EXCL_START
882 JLOG(j.error()) << "unlockEscrowMPT: overflow on MPTAmount for " << to_string(receiver)
883 << ": " << current << " + " << delta;
884 return tecINTERNAL;
885 } // LCOV_EXCL_STOP
886
887 (*sle)[sfMPTAmount] += delta;
888 view.update(sle);
889 }
890 else
891 {
892 // Decrease the Issuance OutstandingAmount
893 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
894 auto const redeem = netAmount.mpt().value();
895
896 // Underflow check for subtraction
897 if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
898 { // LCOV_EXCL_START
899 JLOG(j.error()) << "unlockEscrowMPT: insufficient outstanding amount for "
900 << mptIssue.getMptID() << ": " << outstanding << " < " << redeem;
901 return tecINTERNAL;
902 } // LCOV_EXCL_STOP
903
904 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
905 view.update(sleIssuance);
906 }
907
908 if (issuer == sender)
909 { // LCOV_EXCL_START
910 JLOG(j.error()) << "unlockEscrowMPT: sender is the issuer, "
911 "cannot unlock MPTs.";
912 return tecINTERNAL;
913 } // LCOV_EXCL_STOP
914 // Decrease the MPT Holder EscrowedAmount
915 auto const mptokenID = keylet::mptoken(mptID.key, sender);
916 auto sle = view.peek(mptokenID);
917 if (!sle)
918 { // LCOV_EXCL_START
919 JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << sender;
920 return tecOBJECT_NOT_FOUND;
921 } // LCOV_EXCL_STOP
922
923 if (!sle->isFieldPresent(sfLockedAmount))
924 { // LCOV_EXCL_START
925 JLOG(j.error()) << "unlockEscrowMPT: no locked amount in MPToken for " << to_string(sender);
926 return tecINTERNAL;
927 } // LCOV_EXCL_STOP
928
929 auto const locked = sle->getFieldU64(sfLockedAmount);
930 auto const delta = grossAmount.mpt().value();
931
932 // Underflow check for subtraction
933 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
934 { // LCOV_EXCL_START
935 JLOG(j.error()) << "unlockEscrowMPT: insufficient locked amount for " << to_string(sender)
936 << ": " << locked << " < " << delta;
937 return tecINTERNAL;
938 } // LCOV_EXCL_STOP
939
940 auto const newLocked = locked - delta;
941 if (newLocked == 0)
942 {
943 sle->makeFieldAbsent(sfLockedAmount);
944 }
945 else
946 {
947 sle->setFieldU64(sfLockedAmount, newLocked);
948 }
949 view.update(sle);
950
951 // Note: The gross amount is the amount that was locked, the net
952 // amount is the amount that is being unlocked. The difference is the fee
953 // that was charged for the transfer. If this difference is greater than
954 // zero, we need to update the outstanding amount.
955 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
956 if (diff != 0)
957 {
958 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
959 // Underflow check for subtraction
960 if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
961 { // LCOV_EXCL_START
962 JLOG(j.error()) << "unlockEscrowMPT: insufficient outstanding amount for "
963 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
964 return tecINTERNAL;
965 } // LCOV_EXCL_STOP
966
967 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
968 view.update(sleIssuance);
969 }
970 return tesSUCCESS;
971}
972
973TER
975 ApplyView& view,
976 MPTID const& mptIssuanceID,
977 AccountID const& account,
978 SLE::Ref sponsorSle,
979 std::uint32_t const flags)
980{
981 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
982
983 auto const ownerNode =
984 view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account));
985
986 if (!ownerNode)
987 return tecDIR_FULL; // LCOV_EXCL_LINE
988
989 auto mptoken = std::make_shared<SLE>(mptokenKey);
990 (*mptoken)[sfAccount] = account;
991 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
992 (*mptoken)[sfFlags] = flags;
993 (*mptoken)[sfOwnerNode] = *ownerNode;
994
995 addSponsorToLedgerEntry(mptoken, sponsorSle);
996
997 view.insert(mptoken);
998
999 return tesSUCCESS;
1000}
1001
1002TER
1004 xrpl::ApplyView& view,
1005 xrpl::MPTIssue const& mptIssue,
1006 xrpl::AccountID const& holder,
1007 SLE::Ref sponsorSle,
1008 std::uint32_t flags,
1010{
1011 if (mptIssue.getIssuer() == holder)
1012 return tesSUCCESS;
1013
1014 auto const mptIssuanceID = keylet::mptokenIssuance(mptIssue.getMptID());
1015 auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
1016 if (!view.exists(mptokenID))
1017 {
1018 if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, flags);
1019 !isTesSuccess(err))
1020 {
1021 return err;
1022 }
1023 auto const sleAcct = view.peek(keylet::account(holder));
1024 if (!sleAcct)
1025 {
1026 return tecINTERNAL;
1027 }
1028
1029 increaseOwnerCount(view, sleAcct, sponsorSle, 1, j);
1030 }
1031 return tesSUCCESS;
1032}
1033
1034TER
1036 xrpl::ApplyView& view,
1037 xrpl::MPTIssue const& mptIssue,
1038 xrpl::AccountID const& holder,
1040{
1041 return checkCreateMPT(view, mptIssue, holder, {}, 0, j);
1042}
1043
1045maxMPTAmount(SLE const& sleIssuance)
1046{
1047 return sleIssuance[~sfMaximumAmount].value_or(kMaxMpTokenAmount);
1048}
1049
1051availableMPTAmount(SLE const& sleIssuance)
1052{
1053 auto const max = maxMPTAmount(sleIssuance);
1054 auto const outstanding = sleIssuance[sfOutstandingAmount];
1055 return max - outstanding;
1056}
1057
1059availableMPTAmount(ReadView const& view, MPTID const& mptID)
1060{
1061 auto const sle = view.read(keylet::mptokenIssuance(mptID));
1062 if (!sle)
1064 return availableMPTAmount(*sle);
1065}
1066
1067bool
1069 std::int64_t sendAmount,
1070 std::uint64_t outstandingAmount,
1071 std::int64_t maximumAmount,
1072 AllowMPTOverflow allowOverflow)
1073{
1074 std::uint64_t const limit = (allowOverflow == AllowMPTOverflow::Yes)
1076 : maximumAmount;
1077 return (sendAmount > maximumAmount || outstandingAmount > (limit - sendAmount));
1078}
1079
1080STAmount
1081issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue)
1082{
1083 STAmount amount{issue};
1084
1085 auto const sle = view.read(keylet::mptokenIssuance(issue));
1086 if (!sle)
1087 return amount;
1088 auto const available = availableMPTAmount(*sle);
1089 return view.balanceHookSelfIssueMPT(issue, available);
1090}
1091
1092void
1094{
1095 auto const available = availableMPTAmount(view, issue);
1096 view.issuerSelfDebitHookMPT(issue, amount, available);
1097}
1098
1099} // namespace xrpl
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream error() const
Definition Journal.h:362
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:141
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.
Definition ApplyView.h:302
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.
Definition ApplyView.h:373
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)
Definition Asset.h:117
constexpr bool holds() const
Definition Asset.h:177
A currency issued by an account.
Definition Issue.h:18
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:135
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:43
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
A view into a ledger.
Definition ReadView.h:41
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
Definition ReadView.h:200
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:182
constexpr TIss const & get() const
MPTAmount mpt() const
Definition STAmount.cpp:302
AccountID const & getIssuer() const
Definition STAmount.h:516
LedgerEntryType getType() const
std::shared_ptr< STLedgerEntry > const & Ref
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
Definition STObject.h:1078
UInt192 getFieldH192(SField const &field) const
Definition STObject.cpp:625
bool isFlag(std::uint32_t) const
Definition STObject.cpp:511
std::uint64_t getFieldU64(SField const &field) const
Definition STObject.cpp:607
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:657
T make_shared(T... args)
T max(T... args)
TER validDomain(ReadView const &view, UInt256 domainID, AccountID const &subject)
Keylet computation functions.
Definition Indexes.h:40
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet unchecked(UInt256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:397
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:591
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
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)
@ tefINTERNAL
Definition TER.h:168
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)
Definition TER.cpp:266
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)
Definition base_uint.h:657
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)
Definition View.cpp:159
STLedgerEntry SLE
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.
Definition STAmount.cpp:465
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.
Definition Protocol.h:377
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)
Definition View.cpp:384
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 ...
Definition UintTypes.h:54
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.
Definition AccountID.h:34
constexpr std::uint16_t kMaxTransferFee
The maximum token transfer fee allowed.
Definition Protocol.h:97
bool canSubtract(STAmount const &amt1, STAmount const &amt2)
Determines if it is safe to subtract one STAmount from another.
Definition STAmount.cpp:542
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
TERSubset< CanCvtToTER > TER
Definition TER.h:654
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.
@ tecDIR_FULL
Definition TER.h:295
@ tecOBJECT_NOT_FOUND
Definition TER.h:334
@ tecNO_AUTH
Definition TER.h:308
@ tecINTERNAL
Definition TER.h:318
@ tecEXPIRED
Definition TER.h:322
@ tecNO_PERMISSION
Definition TER.h:313
@ tecDUPLICATE
Definition TER.h:323
@ tecHAS_OBLIGATIONS
Definition TER.h:325
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.
Definition Protocol.h:297
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.
@ tesSUCCESS
Definition TER.h:250
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
Bundles the mutable ledger view and the transaction being applied.
Definition ApplyView.h:451
Represents a transfer rate.
Definition Rate.h:21