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