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