rippled
Loading...
Searching...
No Matches
MPTokenHelpers.cpp
1#include <xrpl/ledger/helpers/MPTokenHelpers.h>
2//
3#include <xrpl/basics/Log.h>
4#include <xrpl/ledger/View.h>
5#include <xrpl/ledger/helpers/AccountRootHelpers.h>
6#include <xrpl/ledger/helpers/CredentialHelpers.h>
7#include <xrpl/ledger/helpers/DirectoryHelpers.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/Indexes.h>
10#include <xrpl/protocol/LedgerFormats.h>
11#include <xrpl/protocol/SField.h>
12#include <xrpl/protocol/TxFlags.h>
13
14namespace xrpl {
15
16bool
17isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
18{
19 if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
20 return sle->isFlag(lsfMPTLocked);
21 return false;
22}
23
24bool
25isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
26{
27 if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
28 return sle->isFlag(lsfMPTLocked);
29 return false;
30}
31
32bool
33isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
34{
35 return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
36 isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
37}
38
39[[nodiscard]] bool
41 ReadView const& view,
43 MPTIssue const& mptIssue,
44 int depth)
45{
46 if (isGlobalFrozen(view, mptIssue))
47 return true;
48
49 for (auto const& account : accounts)
50 {
51 if (isIndividualFrozen(view, account, mptIssue))
52 return true;
53 }
54
55 for (auto const& account : accounts)
56 {
57 if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
58 return true;
59 }
60
61 return false;
62}
63
64Rate
65transferRate(ReadView const& view, MPTID const& issuanceID)
66{
67 // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
68 // For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
69 // which represents 50% of 1,000,000,000
70 if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
71 sle && sle->isFieldPresent(sfTransferFee))
72 return Rate{1'000'000'000u + (10'000 * sle->getFieldU16(sfTransferFee))};
73
74 return parityRate;
75}
76
77[[nodiscard]] TER
78canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
79{
80 auto mptID = mptIssue.getMptID();
81 auto issuance = view.read(keylet::mptIssuance(mptID));
82 if (!issuance)
83 {
85 }
86 if (!issuance->isFlag(lsfMPTCanTransfer))
87 {
88 return tecNO_AUTH;
89 }
90
91 return tesSUCCESS;
92}
93
94[[nodiscard]] TER
96 ApplyView& view,
97 AccountID const& accountID,
98 XRPAmount priorBalance,
99 MPTIssue const& mptIssue,
100 beast::Journal journal)
101{
102 auto const& mptID = mptIssue.getMptID();
103 auto const mpt = view.peek(keylet::mptIssuance(mptID));
104 if (!mpt)
105 return tefINTERNAL; // LCOV_EXCL_LINE
106 if (mpt->isFlag(lsfMPTLocked))
107 return tefINTERNAL; // LCOV_EXCL_LINE
108 if (view.peek(keylet::mptoken(mptID, accountID)))
109 return tecDUPLICATE;
110 if (accountID == mptIssue.getIssuer())
111 return tesSUCCESS;
112
113 return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
114}
115
116[[nodiscard]] TER
118 ApplyView& view,
119 XRPAmount const& priorBalance,
120 MPTID const& mptIssuanceID,
121 AccountID const& account,
122 beast::Journal journal,
123 std::uint32_t flags,
125{
126 auto const sleAcct = view.peek(keylet::account(account));
127 if (!sleAcct)
128 return tecINTERNAL; // LCOV_EXCL_LINE
129
130 // If the account that submitted the tx is a holder
131 // Note: `account_` is holder's account
132 // `holderID` is NOT used
133 if (!holderID)
134 {
135 // When a holder wants to unauthorize/delete a MPT, the ledger must
136 // - delete mptokenKey from owner directory
137 // - delete the MPToken
138 if ((flags & tfMPTUnauthorize) != 0)
139 {
140 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
141 auto const sleMpt = view.peek(mptokenKey);
142 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
143 return tecINTERNAL; // LCOV_EXCL_LINE
144
145 if (!view.dirRemove(
146 keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
147 return tecINTERNAL; // LCOV_EXCL_LINE
148
149 adjustOwnerCount(view, sleAcct, -1, journal);
150
151 view.erase(sleMpt);
152 return tesSUCCESS;
153 }
154
155 // A potential holder wants to authorize/hold a mpt, the ledger must:
156 // - add the new mptokenKey to the owner directory
157 // - create the MPToken object for the holder
158
159 // The reserve that is required to create the MPToken. Note
160 // that although the reserve increases with every item
161 // an account owns, in the case of MPTokens we only
162 // *enforce* a reserve if the user owns more than two
163 // items. This is similar to the reserve requirements of trust lines.
164 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
165 XRPAmount const reserveCreate(
166 (uOwnerCount < 2) ? XRPAmount(beast::zero)
167 : view.fees().accountReserve(uOwnerCount + 1));
168
169 if (priorBalance < reserveCreate)
171
172 // Defensive check before we attempt to create MPToken for the issuer
173 auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
174 if (!mpt || mpt->getAccountID(sfIssuer) == account)
175 {
176 // LCOV_EXCL_START
177 UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
178 if (view.rules().enabled(featureLendingProtocol))
179 return tecINTERNAL;
180 // LCOV_EXCL_STOP
181 }
182
183 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
184 auto mptoken = std::make_shared<SLE>(mptokenKey);
185 if (auto ter = dirLink(view, account, mptoken))
186 return ter; // LCOV_EXCL_LINE
187
188 (*mptoken)[sfAccount] = account;
189 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
190 (*mptoken)[sfFlags] = 0;
191 view.insert(mptoken);
192
193 // Update owner count.
194 adjustOwnerCount(view, sleAcct, 1, journal);
195
196 return tesSUCCESS;
197 }
198
199 auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
200 if (!sleMptIssuance)
201 return tecINTERNAL; // LCOV_EXCL_LINE
202
203 // If the account that submitted this tx is the issuer of the MPT
204 // Note: `account_` is issuer's account
205 // `holderID` is holder's account
206 if (account != (*sleMptIssuance)[sfIssuer])
207 return tecINTERNAL; // LCOV_EXCL_LINE
208
209 auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
210 if (!sleMpt)
211 return tecINTERNAL; // LCOV_EXCL_LINE
212
213 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
214 std::uint32_t flagsOut = flagsIn;
215
216 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
217 // their MPToken
218 if ((flags & tfMPTUnauthorize) != 0)
219 {
220 flagsOut &= ~lsfMPTAuthorized;
221 }
222 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
223 // MPToken
224 else
225 {
226 flagsOut |= lsfMPTAuthorized;
227 }
228
229 if (flagsIn != flagsOut)
230 sleMpt->setFieldU32(sfFlags, flagsOut);
231
232 view.update(sleMpt);
233 return tesSUCCESS;
234}
235
236[[nodiscard]] TER
238 ApplyView& view,
239 AccountID const& accountID,
240 MPTIssue const& mptIssue,
241 beast::Journal journal)
242{
243 // If the account is the issuer, then no token should exist. MPTs do not
244 // have the legacy ability to create such a situation, but check anyway. If
245 // a token does exist, it will get deleted. If not, return success.
246 bool const accountIsIssuer = accountID == mptIssue.getIssuer();
247 auto const& mptID = mptIssue.getMptID();
248 auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
249 if (!mptoken)
250 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
251 // Unlike a trust line, if the account is the issuer, and the token has a
252 // balance, it can not just be deleted, because that will throw the issuance
253 // accounting out of balance, so fail. Since this should be impossible
254 // anyway, I'm not going to put any effort into it.
255 if (mptoken->at(sfMPTAmount) != 0)
256 return tecHAS_OBLIGATIONS;
257
258 return authorizeMPToken(
259 view,
260 {}, // priorBalance
261 mptID,
262 accountID,
263 journal,
264 tfMPTUnauthorize // flags
265 );
266}
267
268[[nodiscard]] TER
270 ReadView const& view,
271 MPTIssue const& mptIssue,
272 AccountID const& account,
273 AuthType authType,
274 int depth)
275{
276 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
277 auto const sleIssuance = view.read(mptID);
278 if (!sleIssuance)
279 return tecOBJECT_NOT_FOUND;
280
281 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
282
283 // issuer is always "authorized"
284 if (mptIssuer == account) // Issuer won't have MPToken
285 return tesSUCCESS;
286
287 bool const featureSAVEnabled = view.rules().enabled(featureSingleAssetVault);
288
289 if (featureSAVEnabled)
290 {
291 if (depth >= maxAssetCheckDepth)
292 return tecINTERNAL; // LCOV_EXCL_LINE
293
294 // requireAuth is recursive if the issuer is a vault pseudo-account
295 auto const sleIssuer = view.read(keylet::account(mptIssuer));
296 if (!sleIssuer)
297 return tefINTERNAL; // LCOV_EXCL_LINE
298
299 if (sleIssuer->isFieldPresent(sfVaultID))
300 {
301 auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
302 if (!sleVault)
303 return tefINTERNAL; // LCOV_EXCL_LINE
304
305 auto const asset = sleVault->at(sfAsset);
306 if (auto const err = std::visit(
307 [&]<ValidIssueType TIss>(TIss const& issue) {
308 if constexpr (std::is_same_v<TIss, Issue>)
309 {
310 return requireAuth(view, issue, account, authType);
311 }
312 else
313 {
314 return requireAuth(view, issue, account, authType, depth + 1);
315 }
316 },
317 asset.value());
318 !isTesSuccess(err))
319 return err;
320 }
321 }
322
323 auto const mptokenID = keylet::mptoken(mptID.key, account);
324 auto const sleToken = view.read(mptokenID);
325
326 // if account has no MPToken, fail
327 if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
328 return tecNO_AUTH;
329
330 // Note, this check is not amendment-gated because DomainID will be always
331 // empty **unless** writing to it has been enabled by an amendment
332 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
333 if (maybeDomainID)
334 {
335 XRPL_ASSERT(
336 sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
337 "xrpl::requireAuth : issuance requires authorization");
338 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
339 auto const ter = credentials::validDomain(view, *maybeDomainID, account);
340 if (isTesSuccess(ter))
341 {
342 return ter; // Note: sleToken might be null
343 }
344 if (!sleToken)
345 {
346 return ter;
347 }
348 // We ignore error from validDomain if we found sleToken, as it could
349 // belong to someone who is explicitly authorized e.g. a vault owner.
350 }
351
352 if (featureSAVEnabled)
353 {
354 // Implicitly authorize Vault and LoanBroker pseudo-accounts
355 if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
356 return tesSUCCESS;
357 }
358
359 // mptoken must be authorized if issuance enabled requireAuth
360 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
361 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
362 return tecNO_AUTH;
363
364 return tesSUCCESS; // Note: sleToken might be null
365}
366
367[[nodiscard]] TER
369 ApplyView& view,
370 MPTID const& mptIssuanceID,
371 AccountID const& account,
372 XRPAmount const& priorBalance, // for MPToken authorization
374{
375 auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
376 if (!sleIssuance)
377 return tefINTERNAL; // LCOV_EXCL_LINE
378
379 XRPL_ASSERT(
380 sleIssuance->isFlag(lsfMPTRequireAuth),
381 "xrpl::enforceMPTokenAuthorization : authorization required");
382
383 if (account == sleIssuance->at(sfIssuer))
384 return tefINTERNAL; // LCOV_EXCL_LINE
385
386 auto const keylet = keylet::mptoken(mptIssuanceID, account);
387 auto const sleToken = view.read(keylet); // NOTE: might be null
388 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
389 bool expired = false;
390 bool const authorizedByDomain = [&]() -> bool {
391 // NOTE: defensive here, should be checked in preclaim
392 if (!maybeDomainID.has_value())
393 return false; // LCOV_EXCL_LINE
394
395 auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
396 if (isTesSuccess(ter))
397 return true;
398 if (ter == tecEXPIRED)
399 expired = true;
400 return false;
401 }();
402
403 if (!authorizedByDomain && sleToken == nullptr)
404 {
405 // Could not find MPToken and won't create one, could be either of:
406 //
407 // 1. Field sfDomainID not set in MPTokenIssuance or
408 // 2. Account has no matching and accepted credentials or
409 // 3. Account has all expired credentials (deleted in verifyValidDomain)
410 //
411 // Either way, return tecNO_AUTH and there is nothing else to do
412 return expired ? tecEXPIRED : tecNO_AUTH;
413 }
414 if (!authorizedByDomain && maybeDomainID.has_value())
415 {
416 // Found an MPToken but the account is not authorized and we expect
417 // it to have been authorized by the domain. This could be because the
418 // credentials used to create the MPToken have expired or been deleted.
419 return expired ? tecEXPIRED : tecNO_AUTH;
420 }
421 if (!authorizedByDomain)
422 {
423 // We found an MPToken, but sfDomainID is not set, so this is a classic
424 // MPToken which requires authorization by the token issuer.
425 XRPL_ASSERT(
426 sleToken != nullptr && !maybeDomainID.has_value(),
427 "xrpl::enforceMPTokenAuthorization : found MPToken");
428 if (sleToken->isFlag(lsfMPTAuthorized))
429 return tesSUCCESS;
430
431 return tecNO_AUTH;
432 }
433 if (authorizedByDomain && sleToken != nullptr)
434 {
435 // Found an MPToken, authorized by the domain. Ignore authorization flag
436 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
437 XRPL_ASSERT(
438 maybeDomainID.has_value(),
439 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
440 return tesSUCCESS;
441 }
442 if (authorizedByDomain)
443 {
444 // Could not find MPToken but there should be one because we are
445 // authorized by domain. Proceed to create it, then return tesSUCCESS
446 XRPL_ASSERT(
447 maybeDomainID.has_value() && sleToken == nullptr,
448 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
449 if (auto const err = authorizeMPToken(
450 view,
451 priorBalance, // priorBalance
452 mptIssuanceID, // mptIssuanceID
453 account, // account
454 j);
455 !isTesSuccess(err))
456 return err;
457
458 return tesSUCCESS;
459 }
460
461 // LCOV_EXCL_START
462 UNREACHABLE("xrpl::enforceMPTokenAuthorization : condition list is incomplete");
463 return tefINTERNAL;
464 // LCOV_EXCL_STOP
465}
466
467TER
469 ReadView const& view,
470 MPTIssue const& mptIssue,
471 AccountID const& from,
472 AccountID const& to)
473{
474 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
475 auto const sleIssuance = view.read(mptID);
476 if (!sleIssuance)
477 return tecOBJECT_NOT_FOUND;
478
479 if (!sleIssuance->isFlag(lsfMPTCanTransfer))
480 {
481 if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
482 return TER{tecNO_AUTH};
483 }
484 return tesSUCCESS;
485}
486
487TER
489 ApplyView& view,
490 AccountID const& sender,
491 STAmount const& amount,
493{
494 auto const mptIssue = amount.get<MPTIssue>();
495 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
496 auto sleIssuance = view.peek(mptID);
497 if (!sleIssuance)
498 { // LCOV_EXCL_START
499 JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
500 << mptIssue.getMptID();
501 return tecOBJECT_NOT_FOUND;
502 } // LCOV_EXCL_STOP
503
504 if (amount.getIssuer() == sender)
505 { // LCOV_EXCL_START
506 JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
507 return tecINTERNAL;
508 } // LCOV_EXCL_STOP
509
510 // 1. Decrease the MPT Holder MPTAmount
511 // 2. Increase the MPT Holder EscrowedAmount
512 {
513 auto const mptokenID = keylet::mptoken(mptID.key, sender);
514 auto sle = view.peek(mptokenID);
515 if (!sle)
516 { // LCOV_EXCL_START
517 JLOG(j.error()) << "rippleLockEscrowMPT: MPToken not found for " << sender;
518 return tecOBJECT_NOT_FOUND;
519 } // LCOV_EXCL_STOP
520
521 auto const amt = sle->getFieldU64(sfMPTAmount);
522 auto const pay = amount.mpt().value();
523
524 // Underflow check for subtraction
525 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
526 { // LCOV_EXCL_START
527 JLOG(j.error()) << "rippleLockEscrowMPT: insufficient MPTAmount for "
528 << to_string(sender) << ": " << amt << " < " << pay;
529 return tecINTERNAL;
530 } // LCOV_EXCL_STOP
531
532 (*sle)[sfMPTAmount] = amt - pay;
533
534 // Overflow check for addition
535 uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
536
537 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
538 { // LCOV_EXCL_START
539 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on locked amount for "
540 << to_string(sender) << ": " << locked << " + " << pay;
541 return tecINTERNAL;
542 } // LCOV_EXCL_STOP
543
544 if (sle->isFieldPresent(sfLockedAmount))
545 {
546 (*sle)[sfLockedAmount] += pay;
547 }
548 else
549 {
550 sle->setFieldU64(sfLockedAmount, pay);
551 }
552
553 view.update(sle);
554 }
555
556 // 1. Increase the Issuance EscrowedAmount
557 // 2. DO NOT change the Issuance OutstandingAmount
558 {
559 uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].value_or(0);
560 auto const pay = amount.mpt().value();
561
562 // Overflow check for addition
563 if (!canAdd(STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
564 { // LCOV_EXCL_START
565 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
566 "locked amount for "
567 << mptIssue.getMptID() << ": " << issuanceEscrowed << " + " << pay;
568 return tecINTERNAL;
569 } // LCOV_EXCL_STOP
570
571 if (sleIssuance->isFieldPresent(sfLockedAmount))
572 {
573 (*sleIssuance)[sfLockedAmount] += pay;
574 }
575 else
576 {
577 sleIssuance->setFieldU64(sfLockedAmount, pay);
578 }
579
580 view.update(sleIssuance);
581 }
582 return tesSUCCESS;
583}
584
585TER
587 ApplyView& view,
588 AccountID const& sender,
589 AccountID const& receiver,
590 STAmount const& netAmount,
591 STAmount const& grossAmount,
593{
594 if (!view.rules().enabled(fixTokenEscrowV1))
595 {
596 XRPL_ASSERT(
597 netAmount == grossAmount, "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
598 }
599
600 auto const& issuer = netAmount.getIssuer();
601 auto const& mptIssue = netAmount.get<MPTIssue>();
602 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
603 auto sleIssuance = view.peek(mptID);
604 if (!sleIssuance)
605 { // LCOV_EXCL_START
606 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
607 << mptIssue.getMptID();
608 return tecOBJECT_NOT_FOUND;
609 } // LCOV_EXCL_STOP
610
611 // Decrease the Issuance EscrowedAmount
612 {
613 if (!sleIssuance->isFieldPresent(sfLockedAmount))
614 { // LCOV_EXCL_START
615 JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in issuance for "
616 << mptIssue.getMptID();
617 return tecINTERNAL;
618 } // LCOV_EXCL_STOP
619
620 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
621 auto const redeem = grossAmount.mpt().value();
622
623 // Underflow check for subtraction
624 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
625 { // LCOV_EXCL_START
626 JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
627 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
628 return tecINTERNAL;
629 } // LCOV_EXCL_STOP
630
631 auto const newLocked = locked - redeem;
632 if (newLocked == 0)
633 {
634 sleIssuance->makeFieldAbsent(sfLockedAmount);
635 }
636 else
637 {
638 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
639 }
640 view.update(sleIssuance);
641 }
642
643 if (issuer != receiver)
644 {
645 // Increase the MPT Holder MPTAmount
646 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
647 auto sle = view.peek(mptokenID);
648 if (!sle)
649 { // LCOV_EXCL_START
650 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
651 return tecOBJECT_NOT_FOUND;
652 } // LCOV_EXCL_STOP
653
654 auto current = sle->getFieldU64(sfMPTAmount);
655 auto delta = netAmount.mpt().value();
656
657 // Overflow check for addition
658 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
659 { // LCOV_EXCL_START
660 JLOG(j.error()) << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
661 << to_string(receiver) << ": " << current << " + " << delta;
662 return tecINTERNAL;
663 } // LCOV_EXCL_STOP
664
665 (*sle)[sfMPTAmount] += delta;
666 view.update(sle);
667 }
668 else
669 {
670 // Decrease the Issuance OutstandingAmount
671 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
672 auto const redeem = netAmount.mpt().value();
673
674 // Underflow check for subtraction
675 if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
676 { // LCOV_EXCL_START
677 JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
678 << mptIssue.getMptID() << ": " << outstanding << " < " << redeem;
679 return tecINTERNAL;
680 } // LCOV_EXCL_STOP
681
682 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
683 view.update(sleIssuance);
684 }
685
686 if (issuer == sender)
687 { // LCOV_EXCL_START
688 JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
689 "cannot unlock MPTs.";
690 return tecINTERNAL;
691 } // LCOV_EXCL_STOP
692 // Decrease the MPT Holder EscrowedAmount
693 auto const mptokenID = keylet::mptoken(mptID.key, sender);
694 auto sle = view.peek(mptokenID);
695 if (!sle)
696 { // LCOV_EXCL_START
697 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
698 return tecOBJECT_NOT_FOUND;
699 } // LCOV_EXCL_STOP
700
701 if (!sle->isFieldPresent(sfLockedAmount))
702 { // LCOV_EXCL_START
703 JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
704 << to_string(sender);
705 return tecINTERNAL;
706 } // LCOV_EXCL_STOP
707
708 auto const locked = sle->getFieldU64(sfLockedAmount);
709 auto const delta = grossAmount.mpt().value();
710
711 // Underflow check for subtraction
712 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
713 { // LCOV_EXCL_START
714 JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
715 << to_string(sender) << ": " << locked << " < " << delta;
716 return tecINTERNAL;
717 } // LCOV_EXCL_STOP
718
719 auto const newLocked = locked - delta;
720 if (newLocked == 0)
721 {
722 sle->makeFieldAbsent(sfLockedAmount);
723 }
724 else
725 {
726 sle->setFieldU64(sfLockedAmount, newLocked);
727 }
728 view.update(sle);
729
730 // Note: The gross amount is the amount that was locked, the net
731 // amount is the amount that is being unlocked. The difference is the fee
732 // that was charged for the transfer. If this difference is greater than
733 // zero, we need to update the outstanding amount.
734 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
735 if (diff != 0)
736 {
737 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
738 // Underflow check for subtraction
739 if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
740 { // LCOV_EXCL_START
741 JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
742 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
743 return tecINTERNAL;
744 } // LCOV_EXCL_STOP
745
746 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
747 view.update(sleIssuance);
748 }
749 return tesSUCCESS;
750}
751
752} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:116
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:114
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:26
AccountID const & getIssuer() const
Definition MPTIssue.cpp:21
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 std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:120
constexpr TIss const & get() const
MPTAmount mpt() const
Definition STAmount.cpp:291
AccountID const & getIssuer() const
Definition STAmount.h:482
T is_same_v
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:336
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:474
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:504
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:486
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::uint8_t constexpr maxAssetCheckDepth
Maximum recursion depth for vault shares being put as an asset inside another vault; counted from 0.
Definition Protocol.h:252
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, int depth)
Definition View.cpp:43
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
@ expired
List is expired, but has the largest non-pending sequence seen so far.
@ tefINTERNAL
Definition TER.h:153
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
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)
@ current
This was a new validation and was added.
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, int depth=0)
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:492
TERSubset< CanCvtToTER > TER
Definition TER.h:622
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:296
bool canSubtract(STAmount const &amt1, STAmount const &amt2)
Determines if it is safe to subtract one STAmount from another.
Definition STAmount.cpp:560
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
@ tecOBJECT_NOT_FOUND
Definition TER.h:307
@ tecNO_AUTH
Definition TER.h:281
@ tecINTERNAL
Definition TER.h:291
@ tecEXPIRED
Definition TER.h:295
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecDUPLICATE
Definition TER.h:296
@ tecHAS_OBLIGATIONS
Definition TER.h:298
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, MPTIssue const &mptIssue, beast::Journal journal)
@ tesSUCCESS
Definition TER.h:225
Rate const parityRate
A transfer rate signifying a 1:1 exchange.
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, int depth=0)
Check if the account lacks required authorization for MPT.
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
uint256 key
Definition Keylet.h:20
Represents a transfer rate.
Definition Rate.h:20
T visit(T... args)