rippled
Loading...
Searching...
No Matches
View.cpp
1#include <xrpl/basics/Expected.h>
2#include <xrpl/basics/Log.h>
3#include <xrpl/basics/chrono.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/ledger/CredentialHelpers.h>
6#include <xrpl/ledger/Credit.h>
7#include <xrpl/ledger/ReadView.h>
8#include <xrpl/ledger/View.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/Indexes.h>
11#include <xrpl/protocol/LedgerFormats.h>
12#include <xrpl/protocol/MPTIssue.h>
13#include <xrpl/protocol/Protocol.h>
14#include <xrpl/protocol/Quality.h>
15#include <xrpl/protocol/TER.h>
16#include <xrpl/protocol/TxFlags.h>
17#include <xrpl/protocol/digest.h>
18#include <xrpl/protocol/st.h>
19
20#include <type_traits>
21#include <variant>
22
23namespace xrpl {
24
25namespace detail {
26
27template <
28 class V,
29 class N,
30 class = std::enable_if_t<
33bool
35 V& view,
36 uint256 const& root,
38 unsigned int& index,
39 uint256& entry)
40{
41 auto const& svIndexes = page->getFieldV256(sfIndexes);
42 XRPL_ASSERT(
43 index <= svIndexes.size(),
44 "xrpl::detail::internalDirNext : index inside range");
45
46 if (index >= svIndexes.size())
47 {
48 auto const next = page->getFieldU64(sfIndexNext);
49
50 if (!next)
51 {
52 entry.zero();
53 return false;
54 }
55
56 if constexpr (std::is_const_v<N>)
57 page = view.read(keylet::page(root, next));
58 else
59 page = view.peek(keylet::page(root, next));
60
61 XRPL_ASSERT(page, "xrpl::detail::internalDirNext : non-null root");
62
63 if (!page)
64 return false;
65
66 index = 0;
67
68 return internalDirNext(view, root, page, index, entry);
69 }
70
71 entry = svIndexes[index++];
72 return true;
73}
74
75template <
76 class V,
77 class N,
78 class = std::enable_if_t<
81bool
83 V& view,
84 uint256 const& root,
86 unsigned int& index,
87 uint256& entry)
88{
89 if constexpr (std::is_const_v<N>)
90 page = view.read(keylet::page(root));
91 else
92 page = view.peek(keylet::page(root));
93
94 if (!page)
95 return false;
96
97 index = 0;
98
99 return internalDirNext(view, root, page, index, entry);
100}
101
102} // namespace detail
103
104bool
106 ApplyView& view,
107 uint256 const& root,
109 unsigned int& index,
110 uint256& entry)
111{
112 return detail::internalDirFirst(view, root, page, index, entry);
113}
114
115bool
117 ApplyView& view,
118 uint256 const& root,
120 unsigned int& index,
121 uint256& entry)
122{
123 return detail::internalDirNext(view, root, page, index, entry);
124}
125
126bool
128 ReadView const& view,
129 uint256 const& root,
131 unsigned int& index,
132 uint256& entry)
133{
134 return detail::internalDirFirst(view, root, page, index, entry);
135}
136
137bool
139 ReadView const& view,
140 uint256 const& root,
142 unsigned int& index,
143 uint256& entry)
144{
145 return detail::internalDirNext(view, root, page, index, entry);
146}
147
148//------------------------------------------------------------------------------
149//
150// Observers
151//
152//------------------------------------------------------------------------------
153
154bool
156{
157 using d = NetClock::duration;
158 using tp = NetClock::time_point;
159
160 return exp && (view.parentCloseTime() >= tp{d{*exp}});
161}
162
163bool
164isGlobalFrozen(ReadView const& view, AccountID const& issuer)
165{
166 if (isXRP(issuer))
167 return false;
168 if (auto const sle = view.read(keylet::account(issuer)))
169 return sle->isFlag(lsfGlobalFreeze);
170 return false;
171}
172
173bool
174isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
175{
176 if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
177 return sle->isFlag(lsfMPTLocked);
178 return false;
179}
180
181bool
182isGlobalFrozen(ReadView const& view, Asset const& asset)
183{
184 return std::visit(
185 [&]<ValidIssueType TIss>(TIss const& issue) {
186 if constexpr (std::is_same_v<TIss, Issue>)
187 return isGlobalFrozen(view, issue.getIssuer());
188 else
189 return isGlobalFrozen(view, issue);
190 },
191 asset.value());
192}
193
194bool
196 ReadView const& view,
197 AccountID const& account,
198 Currency const& currency,
199 AccountID const& issuer)
200{
201 if (isXRP(currency))
202 return false;
203 if (issuer != account)
204 {
205 // Check if the issuer froze the line
206 auto const sle = view.read(keylet::line(account, issuer, currency));
207 if (sle &&
208 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
209 return true;
210 }
211 return false;
212}
213
214bool
216 ReadView const& view,
217 AccountID const& account,
218 MPTIssue const& mptIssue)
219{
220 if (auto const sle =
221 view.read(keylet::mptoken(mptIssue.getMptID(), account)))
222 return sle->isFlag(lsfMPTLocked);
223 return false;
224}
225
226// Can the specified account spend the specified currency issued by
227// the specified issuer or does the freeze flag prohibit it?
228bool
230 ReadView const& view,
231 AccountID const& account,
232 Currency const& currency,
233 AccountID const& issuer)
234{
235 if (isXRP(currency))
236 return false;
237 auto sle = view.read(keylet::account(issuer));
238 if (sle && sle->isFlag(lsfGlobalFreeze))
239 return true;
240 if (issuer != account)
241 {
242 // Check if the issuer froze the line
243 sle = view.read(keylet::line(account, issuer, currency));
244 if (sle &&
245 sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
246 return true;
247 }
248 return false;
249}
250
251bool
253 ReadView const& view,
254 AccountID const& account,
255 MPTIssue const& mptIssue,
256 int depth)
257{
258 return isGlobalFrozen(view, mptIssue) ||
259 isIndividualFrozen(view, account, mptIssue) ||
260 isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
261}
262
263[[nodiscard]] bool
265 ReadView const& view,
266 std::initializer_list<AccountID> const& accounts,
267 MPTIssue const& mptIssue,
268 int depth)
269{
270 if (isGlobalFrozen(view, mptIssue))
271 return true;
272
273 for (auto const& account : accounts)
274 {
275 if (isIndividualFrozen(view, account, mptIssue))
276 return true;
277 }
278
279 for (auto const& account : accounts)
280 {
281 if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
282 return true;
283 }
284
285 return false;
286}
287
288bool
290 ReadView const& view,
291 AccountID const& account,
292 MPTIssue const& mptShare,
293 int depth)
294{
295 if (!view.rules().enabled(featureSingleAssetVault))
296 return false;
297
298 if (depth >= maxAssetCheckDepth)
299 return true; // LCOV_EXCL_LINE
300
301 auto const mptIssuance =
302 view.read(keylet::mptIssuance(mptShare.getMptID()));
303 if (mptIssuance == nullptr)
304 return false; // zero MPToken won't block deletion of MPTokenIssuance
305
306 auto const issuer = mptIssuance->getAccountID(sfIssuer);
307 auto const mptIssuer = view.read(keylet::account(issuer));
308 if (mptIssuer == nullptr)
309 {
310 // LCOV_EXCL_START
311 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
312 return false;
313 // LCOV_EXCL_STOP
314 }
315
316 if (!mptIssuer->isFieldPresent(sfVaultID))
317 return false; // not a Vault pseudo-account, common case
318
319 auto const vault =
320 view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
321 if (vault == nullptr)
322 { // LCOV_EXCL_START
323 UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null vault");
324 return false;
325 // LCOV_EXCL_STOP
326 }
327
328 return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
329}
330
331bool
333 ReadView const& view,
334 AccountID const& account,
335 Currency const& currency,
336 AccountID const& issuer)
337{
338 if (isXRP(currency))
339 {
340 return false;
341 }
342
343 if (issuer == account)
344 {
345 return false;
346 }
347
348 auto const sle = view.read(keylet::line(account, issuer, currency));
349 if (!sle)
350 {
351 return false;
352 }
353
354 return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
355}
356
357bool
359 ReadView const& view,
360 AccountID const& account,
361 Issue const& asset,
362 Issue const& asset2)
363{
364 return isFrozen(view, account, asset.currency, asset.account) ||
365 isFrozen(view, account, asset2.currency, asset2.account);
366}
367
370 ReadView const& view,
371 AccountID const& account,
372 Currency const& currency,
373 AccountID const& issuer,
374 FreezeHandling zeroIfFrozen,
376{
377 auto const sle = view.read(keylet::line(account, issuer, currency));
378
379 if (!sle)
380 {
381 return nullptr;
382 }
383
384 if (zeroIfFrozen == fhZERO_IF_FROZEN)
385 {
386 if (isFrozen(view, account, currency, issuer) ||
387 isDeepFrozen(view, account, currency, issuer))
388 {
389 return nullptr;
390 }
391
392 // when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
393 // we need to check if the associated assets have been frozen
394 if (view.rules().enabled(fixFrozenLPTokenTransfer))
395 {
396 auto const sleIssuer = view.read(keylet::account(issuer));
397 if (!sleIssuer)
398 {
399 return nullptr; // LCOV_EXCL_LINE
400 }
401 else if (sleIssuer->isFieldPresent(sfAMMID))
402 {
403 auto const sleAmm =
404 view.read(keylet::amm((*sleIssuer)[sfAMMID]));
405
406 if (!sleAmm ||
408 view,
409 account,
410 (*sleAmm)[sfAsset].get<Issue>(),
411 (*sleAmm)[sfAsset2].get<Issue>()))
412 {
413 return nullptr;
414 }
415 }
416 }
417 }
418
419 return sle;
420}
421
422static STAmount
424 ReadView const& view,
425 SLE::const_ref sle,
426 AccountID const& account,
427 Currency const& currency,
428 AccountID const& issuer,
429 bool includeOppositeLimit,
431{
432 STAmount amount;
433 if (sle)
434 {
435 amount = sle->getFieldAmount(sfBalance);
436 bool const accountHigh = account > issuer;
437 auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
438 if (accountHigh)
439 {
440 // Put balance in account terms.
441 amount.negate();
442 }
443 if (includeOppositeLimit)
444 {
445 amount += sle->getFieldAmount(oppositeField);
446 }
447 amount.setIssuer(issuer);
448 }
449 else
450 {
451 amount.clear(Issue{currency, issuer});
452 }
453
454 JLOG(j.trace()) << "getTrustLineBalance:" << " account="
455 << to_string(account) << " amount=" << amount.getFullText();
456
457 return view.balanceHook(account, issuer, amount);
458}
459
460STAmount
462 ReadView const& view,
463 AccountID const& account,
464 Currency const& currency,
465 AccountID const& issuer,
466 FreezeHandling zeroIfFrozen,
468 SpendableHandling includeFullBalance)
469{
470 STAmount amount;
471 if (isXRP(currency))
472 {
473 return {xrpLiquid(view, account, 0, j)};
474 }
475
476 bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
477 if (returnSpendable && account == issuer)
478 // If the account is the issuer, then their limit is effectively
479 // infinite
480 return STAmount{
482
483 // IOU: Return balance on trust line modulo freeze
484 SLE::const_pointer const sle =
485 getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j);
486
487 return getTrustLineBalance(
488 view, sle, account, currency, issuer, returnSpendable, j);
489}
490
491STAmount
493 ReadView const& view,
494 AccountID const& account,
495 Issue const& issue,
496 FreezeHandling zeroIfFrozen,
498 SpendableHandling includeFullBalance)
499{
500 return accountHolds(
501 view,
502 account,
503 issue.currency,
504 issue.account,
505 zeroIfFrozen,
506 j,
507 includeFullBalance);
508}
509
510STAmount
512 ReadView const& view,
513 AccountID const& account,
514 MPTIssue const& mptIssue,
515 FreezeHandling zeroIfFrozen,
516 AuthHandling zeroIfUnauthorized,
518 SpendableHandling includeFullBalance)
519{
520 bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
521
522 if (returnSpendable && account == mptIssue.getIssuer())
523 {
524 // if the account is the issuer, and the issuance exists, their limit is
525 // the issuance limit minus the outstanding value
526 auto const issuance =
527 view.read(keylet::mptIssuance(mptIssue.getMptID()));
528
529 if (!issuance)
530 {
531 return STAmount{mptIssue};
532 }
533 return STAmount{
534 mptIssue,
535 issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
536 issuance->at(sfOutstandingAmount)};
537 }
538
539 STAmount amount;
540
541 auto const sleMpt =
542 view.read(keylet::mptoken(mptIssue.getMptID(), account));
543
544 if (!sleMpt)
545 amount.clear(mptIssue);
546 else if (
547 zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue))
548 amount.clear(mptIssue);
549 else
550 {
551 amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
552
553 // Only if auth check is needed, as it needs to do an additional read
554 // operation. Note featureSingleAssetVault will affect error codes.
555 if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
556 view.rules().enabled(featureSingleAssetVault))
557 {
558 if (auto const err =
559 requireAuth(view, mptIssue, account, AuthType::StrongAuth);
560 !isTesSuccess(err))
561 amount.clear(mptIssue);
562 }
563 else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
564 {
565 auto const sleIssuance =
566 view.read(keylet::mptIssuance(mptIssue.getMptID()));
567
568 // if auth is enabled on the issuance and mpt is not authorized,
569 // clear amount
570 if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
571 !sleMpt->isFlag(lsfMPTAuthorized))
572 amount.clear(mptIssue);
573 }
574 }
575
576 return amount;
577}
578
579[[nodiscard]] STAmount
581 ReadView const& view,
582 AccountID const& account,
583 Asset const& asset,
584 FreezeHandling zeroIfFrozen,
585 AuthHandling zeroIfUnauthorized,
587 SpendableHandling includeFullBalance)
588{
589 return std::visit(
590 [&]<ValidIssueType TIss>(TIss const& value) {
591 if constexpr (std::is_same_v<TIss, Issue>)
592 {
593 return accountHolds(
594 view, account, value, zeroIfFrozen, j, includeFullBalance);
595 }
596 else if constexpr (std::is_same_v<TIss, MPTIssue>)
597 {
598 return accountHolds(
599 view,
600 account,
601 value,
602 zeroIfFrozen,
603 zeroIfUnauthorized,
604 j,
605 includeFullBalance);
606 }
607 },
608 asset.value());
609}
610
611STAmount
613 ReadView const& view,
614 AccountID const& id,
615 STAmount const& saDefault,
616 FreezeHandling freezeHandling,
618{
619 if (!saDefault.native() && saDefault.getIssuer() == id)
620 return saDefault;
621
622 return accountHolds(
623 view,
624 id,
625 saDefault.getCurrency(),
626 saDefault.getIssuer(),
627 freezeHandling,
628 j);
629}
630
631// Prevent ownerCount from wrapping under error conditions.
632//
633// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
634// If id != std::nullopt, then do error reporting.
635//
636// Returns adjusted owner count.
637static std::uint32_t
640 std::int32_t adjustment,
643{
644 std::uint32_t adjusted{current + adjustment};
645 if (adjustment > 0)
646 {
647 // Overflow is well defined on unsigned
648 if (adjusted < current)
649 {
650 if (id)
651 {
652 JLOG(j.fatal())
653 << "Account " << *id << " owner count exceeds max!";
654 }
656 }
657 }
658 else
659 {
660 // Underflow is well defined on unsigned
661 if (adjusted > current)
662 {
663 if (id)
664 {
665 JLOG(j.fatal())
666 << "Account " << *id << " owner count set below 0!";
667 }
668 adjusted = 0;
669 XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
670 }
671 }
672 return adjusted;
673}
674
675XRPAmount
677 ReadView const& view,
678 AccountID const& id,
679 std::int32_t ownerCountAdj,
681{
682 auto const sle = view.read(keylet::account(id));
683 if (sle == nullptr)
684 return beast::zero;
685
686 // Return balance minus reserve
687 std::uint32_t const ownerCount = confineOwnerCount(
688 view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
689
690 // Pseudo-accounts have no reserve requirement
691 auto const reserve = isPseudoAccount(sle)
692 ? XRPAmount{0}
693 : view.fees().accountReserve(ownerCount);
694
695 auto const fullBalance = sle->getFieldAmount(sfBalance);
696
697 auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
698
699 STAmount const amount =
700 (balance < reserve) ? STAmount{0} : balance - reserve;
701
702 JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
703 << " amount=" << amount.getFullText()
704 << " fullBalance=" << fullBalance.getFullText()
705 << " balance=" << balance.getFullText()
706 << " reserve=" << reserve << " ownerCount=" << ownerCount
707 << " ownerCountAdj=" << ownerCountAdj;
708
709 return amount.xrp();
710}
711
712void
714 ReadView const& view,
715 Keylet const& root,
716 std::function<void(std::shared_ptr<SLE const> const&)> const& f)
717{
718 XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItem : valid root type");
719
720 if (root.type != ltDIR_NODE)
721 return;
722
723 auto pos = root;
724
725 while (true)
726 {
727 auto sle = view.read(pos);
728 if (!sle)
729 return;
730 for (auto const& key : sle->getFieldV256(sfIndexes))
731 f(view.read(keylet::child(key)));
732 auto const next = sle->getFieldU64(sfIndexNext);
733 if (!next)
734 return;
735 pos = keylet::page(root, next);
736 }
737}
738
739bool
741 ReadView const& view,
742 Keylet const& root,
743 uint256 const& after,
744 std::uint64_t const hint,
745 unsigned int limit,
746 std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
747{
748 XRPL_ASSERT(
749 root.type == ltDIR_NODE, "xrpl::forEachItemAfter : valid root type");
750
751 if (root.type != ltDIR_NODE)
752 return false;
753
754 auto currentIndex = root;
755
756 // If startAfter is not zero try jumping to that page using the hint
757 if (after.isNonZero())
758 {
759 auto const hintIndex = keylet::page(root, hint);
760
761 if (auto hintDir = view.read(hintIndex))
762 {
763 for (auto const& key : hintDir->getFieldV256(sfIndexes))
764 {
765 if (key == after)
766 {
767 // We found the hint, we can start here
768 currentIndex = hintIndex;
769 break;
770 }
771 }
772 }
773
774 bool found = false;
775 for (;;)
776 {
777 auto const ownerDir = view.read(currentIndex);
778 if (!ownerDir)
779 return found;
780 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
781 {
782 if (!found)
783 {
784 if (key == after)
785 found = true;
786 }
787 else if (f(view.read(keylet::child(key))) && limit-- <= 1)
788 {
789 return found;
790 }
791 }
792
793 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
794 if (uNodeNext == 0)
795 return found;
796 currentIndex = keylet::page(root, uNodeNext);
797 }
798 }
799 else
800 {
801 for (;;)
802 {
803 auto const ownerDir = view.read(currentIndex);
804 if (!ownerDir)
805 return true;
806 for (auto const& key : ownerDir->getFieldV256(sfIndexes))
807 if (f(view.read(keylet::child(key))) && limit-- <= 1)
808 return true;
809 auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
810 if (uNodeNext == 0)
811 return true;
812 currentIndex = keylet::page(root, uNodeNext);
813 }
814 }
815}
816
817Rate
818transferRate(ReadView const& view, AccountID const& issuer)
819{
820 auto const sle = view.read(keylet::account(issuer));
821
822 if (sle && sle->isFieldPresent(sfTransferRate))
823 return Rate{sle->getFieldU32(sfTransferRate)};
824
825 return parityRate;
826}
827
828Rate
829transferRate(ReadView const& view, MPTID const& issuanceID)
830{
831 // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
832 // For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
833 // which represents 50% of 1,000,000,000
834 if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
835 sle && sle->isFieldPresent(sfTransferFee))
836 return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
837
838 return parityRate;
839}
840
841Rate
842transferRate(ReadView const& view, STAmount const& amount)
843{
844 return std::visit(
845 [&]<ValidIssueType TIss>(TIss const& issue) {
846 if constexpr (std::is_same_v<TIss, Issue>)
847 return transferRate(view, issue.getIssuer());
848 else
849 return transferRate(view, issue.getMptID());
850 },
851 amount.asset().value());
852}
853
854bool
856 ReadView const& validLedger,
857 ReadView const& testLedger,
859 char const* reason)
860{
861 bool ret = true;
862
863 if (validLedger.header().seq < testLedger.header().seq)
864 {
865 // valid -> ... -> test
866 auto hash = hashOfSeq(
867 testLedger,
868 validLedger.header().seq,
869 beast::Journal{beast::Journal::getNullSink()});
870 if (hash && (*hash != validLedger.header().hash))
871 {
872 JLOG(s) << reason << " incompatible with valid ledger";
873
874 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
875
876 ret = false;
877 }
878 }
879 else if (validLedger.header().seq > testLedger.header().seq)
880 {
881 // test -> ... -> valid
882 auto hash = hashOfSeq(
883 validLedger,
884 testLedger.header().seq,
885 beast::Journal{beast::Journal::getNullSink()});
886 if (hash && (*hash != testLedger.header().hash))
887 {
888 JLOG(s) << reason << " incompatible preceding ledger";
889
890 JLOG(s) << "Hash(NSeq): " << to_string(*hash);
891
892 ret = false;
893 }
894 }
895 else if (
896 (validLedger.header().seq == testLedger.header().seq) &&
897 (validLedger.header().hash != testLedger.header().hash))
898 {
899 // Same sequence number, different hash
900 JLOG(s) << reason << " incompatible ledger";
901
902 ret = false;
903 }
904
905 if (!ret)
906 {
907 JLOG(s) << "Val: " << validLedger.header().seq << " "
908 << to_string(validLedger.header().hash);
909
910 JLOG(s) << "New: " << testLedger.header().seq << " "
911 << to_string(testLedger.header().hash);
912 }
913
914 return ret;
915}
916
917bool
919 uint256 const& validHash,
920 LedgerIndex validIndex,
921 ReadView const& testLedger,
923 char const* reason)
924{
925 bool ret = true;
926
927 if (testLedger.header().seq > validIndex)
928 {
929 // Ledger we are testing follows last valid ledger
930 auto hash = hashOfSeq(
931 testLedger,
932 validIndex,
934 if (hash && (*hash != validHash))
935 {
936 JLOG(s) << reason << " incompatible following ledger";
937 JLOG(s) << "Hash(VSeq): " << to_string(*hash);
938
939 ret = false;
940 }
941 }
942 else if (
943 (validIndex == testLedger.header().seq) &&
944 (testLedger.header().hash != validHash))
945 {
946 JLOG(s) << reason << " incompatible ledger";
947
948 ret = false;
949 }
950
951 if (!ret)
952 {
953 JLOG(s) << "Val: " << validIndex << " " << to_string(validHash);
954
955 JLOG(s) << "New: " << testLedger.header().seq << " "
956 << to_string(testLedger.header().hash);
957 }
958
959 return ret;
960}
961
962bool
963dirIsEmpty(ReadView const& view, Keylet const& k)
964{
965 auto const sleNode = view.read(k);
966 if (!sleNode)
967 return true;
968 if (!sleNode->getFieldV256(sfIndexes).empty())
969 return false;
970 // The first page of a directory may legitimately be empty even if there
971 // are other pages (the first page is the anchor page) so check to see if
972 // there is another page. If there is, the directory isn't empty.
973 return sleNode->getFieldU64(sfIndexNext) == 0;
974}
975
978{
979 std::set<uint256> amendments;
980
981 if (auto const sle = view.read(keylet::amendments()))
982 {
983 if (sle->isFieldPresent(sfAmendments))
984 {
985 auto const& v = sle->getFieldV256(sfAmendments);
986 amendments.insert(v.begin(), v.end());
987 }
988 }
989
990 return amendments;
991}
992
995{
997
998 if (auto const sle = view.read(keylet::amendments()))
999 {
1000 if (sle->isFieldPresent(sfMajorities))
1001 {
1002 using tp = NetClock::time_point;
1003 using d = tp::duration;
1004
1005 auto const majorities = sle->getFieldArray(sfMajorities);
1006
1007 for (auto const& m : majorities)
1008 ret[m.getFieldH256(sfAmendment)] =
1009 tp(d(m.getFieldU32(sfCloseTime)));
1010 }
1011 }
1012
1013 return ret;
1014}
1015
1017hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
1018{
1019 // Easy cases...
1020 if (seq > ledger.seq())
1021 {
1022 JLOG(journal.warn())
1023 << "Can't get seq " << seq << " from " << ledger.seq() << " future";
1024 return std::nullopt;
1025 }
1026 if (seq == ledger.seq())
1027 return ledger.header().hash;
1028 if (seq == (ledger.seq() - 1))
1029 return ledger.header().parentHash;
1030
1031 if (int diff = ledger.seq() - seq; diff <= 256)
1032 {
1033 // Within 256...
1034 auto const hashIndex = ledger.read(keylet::skip());
1035 if (hashIndex)
1036 {
1037 XRPL_ASSERT(
1038 hashIndex->getFieldU32(sfLastLedgerSequence) ==
1039 (ledger.seq() - 1),
1040 "xrpl::hashOfSeq : matching ledger sequence");
1041 STVector256 vec = hashIndex->getFieldV256(sfHashes);
1042 if (vec.size() >= diff)
1043 return vec[vec.size() - diff];
1044 JLOG(journal.warn())
1045 << "Ledger " << ledger.seq() << " missing hash for " << seq
1046 << " (" << vec.size() << "," << diff << ")";
1047 }
1048 else
1049 {
1050 JLOG(journal.warn())
1051 << "Ledger " << ledger.seq() << ":" << ledger.header().hash
1052 << " missing normal list";
1053 }
1054 }
1055
1056 if ((seq & 0xff) != 0)
1057 {
1058 JLOG(journal.debug())
1059 << "Can't get seq " << seq << " from " << ledger.seq() << " past";
1060 return std::nullopt;
1061 }
1062
1063 // in skiplist
1064 auto const hashIndex = ledger.read(keylet::skip(seq));
1065 if (hashIndex)
1066 {
1067 auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
1068 XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
1069 XRPL_ASSERT(
1070 (lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
1071 auto const diff = (lastSeq - seq) >> 8;
1072 STVector256 vec = hashIndex->getFieldV256(sfHashes);
1073 if (vec.size() > diff)
1074 return vec[vec.size() - diff - 1];
1075 }
1076 JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq()
1077 << " error";
1078 return std::nullopt;
1079}
1080
1081//------------------------------------------------------------------------------
1082//
1083// Modifiers
1084//
1085//------------------------------------------------------------------------------
1086
1087void
1089 ApplyView& view,
1090 std::shared_ptr<SLE> const& sle,
1091 std::int32_t amount,
1093{
1094 if (!sle)
1095 return;
1096 XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
1097 std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
1098 AccountID const id = (*sle)[sfAccount];
1099 std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
1100 view.adjustOwnerCountHook(id, current, adjusted);
1101 sle->at(sfOwnerCount) = adjusted;
1102 view.update(sle);
1103}
1104
1107{
1108 return [account](std::shared_ptr<SLE> const& sle) {
1109 (*sle)[sfOwner] = account;
1110 };
1111}
1112
1113TER
1115 ApplyView& view,
1116 AccountID const& owner,
1117 std::shared_ptr<SLE>& object,
1118 SF_UINT64 const& node)
1119{
1120 auto const page = view.dirInsert(
1121 keylet::ownerDir(owner), object->key(), describeOwnerDir(owner));
1122 if (!page)
1123 return tecDIR_FULL; // LCOV_EXCL_LINE
1124 object->setFieldU64(node, *page);
1125 return tesSUCCESS;
1126}
1127
1129pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
1130{
1131 // This number must not be changed without an amendment
1132 constexpr std::uint16_t maxAccountAttempts = 256;
1133 for (std::uint16_t i = 0; i < maxAccountAttempts; ++i)
1134 {
1135 ripesha_hasher rsh;
1136 auto const hash =
1137 sha512Half(i, view.header().parentHash, pseudoOwnerKey);
1138 rsh(hash.data(), hash.size());
1139 AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
1140 if (!view.read(keylet::account(ret)))
1141 return ret;
1142 }
1143 return beast::zero;
1144}
1145
1146// Pseudo-account designator fields MUST be maintained by including the
1147// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
1148// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
1149// since a non-active amendment will not set any field, by definition.
1150// Specific properties of a pseudo-account are NOT checked here, that's what
1151// InvariantCheck is for.
1152[[nodiscard]] std::vector<SField const*> const&
1154{
1155 static std::vector<SField const*> const pseudoFields = []() {
1156 auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT);
1157 if (!ar)
1158 {
1159 // LCOV_EXCL_START
1160 LogicError(
1161 "xrpl::getPseudoAccountFields : unable to find account root "
1162 "ledger format");
1163 // LCOV_EXCL_STOP
1164 }
1165 auto const& soTemplate = ar->getSOTemplate();
1166
1167 std::vector<SField const*> pseudoFields;
1168 for (auto const& field : soTemplate)
1169 {
1170 if (field.sField().shouldMeta(SField::sMD_PseudoAccount))
1171 pseudoFields.emplace_back(&field.sField());
1172 }
1173 return pseudoFields;
1174 }();
1175 return pseudoFields;
1176}
1177
1178[[nodiscard]] bool
1181 std::set<SField const*> const& pseudoFieldFilter)
1182{
1183 auto const& fields = getPseudoAccountFields();
1184
1185 // Intentionally use defensive coding here because it's cheap and makes the
1186 // semantics of true return value clean.
1187 return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
1189 fields.begin(),
1190 fields.end(),
1191 [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
1192 return sleAcct->isFieldPresent(*sf) &&
1193 (pseudoFieldFilter.empty() ||
1194 pseudoFieldFilter.contains(sf));
1195 }) > 0;
1196}
1197
1198Expected<std::shared_ptr<SLE>, TER>
1200 ApplyView& view,
1201 uint256 const& pseudoOwnerKey,
1202 SField const& ownerField)
1203{
1204 [[maybe_unused]]
1205 auto const& fields = getPseudoAccountFields();
1206 XRPL_ASSERT(
1208 fields.begin(),
1209 fields.end(),
1210 [&ownerField](SField const* sf) -> bool {
1211 return *sf == ownerField;
1212 }) == 1,
1213 "xrpl::createPseudoAccount : valid owner field");
1214
1215 auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey);
1216 if (accountId == beast::zero)
1217 return Unexpected(tecDUPLICATE);
1218
1219 // Create pseudo-account.
1220 auto account = std::make_shared<SLE>(keylet::account(accountId));
1221 account->setAccountID(sfAccount, accountId);
1222 account->setFieldAmount(sfBalance, STAmount{});
1223
1224 // Pseudo-accounts can't submit transactions, so set the sequence number
1225 // to 0 to make them easier to spot and verify, and add an extra level
1226 // of protection.
1227 std::uint32_t const seqno = //
1228 view.rules().enabled(featureSingleAssetVault) || //
1229 view.rules().enabled(featureLendingProtocol) //
1230 ? 0 //
1231 : view.seq();
1232 account->setFieldU32(sfSequence, seqno);
1233 // Ignore reserves requirement, disable the master key, allow default
1234 // rippling, and enable deposit authorization to prevent payments into
1235 // pseudo-account.
1236 account->setFieldU32(
1238 // Link the pseudo-account with its owner object.
1239 account->setFieldH256(ownerField, pseudoOwnerKey);
1240
1241 view.insert(account);
1242
1243 return account;
1244}
1245
1246[[nodiscard]] TER
1247canAddHolding(ReadView const& view, Issue const& issue)
1248{
1249 if (issue.native())
1250 return tesSUCCESS; // No special checks for XRP
1251
1252 auto const issuer = view.read(keylet::account(issue.getIssuer()));
1253 if (!issuer)
1254 return terNO_ACCOUNT;
1255 else if (!issuer->isFlag(lsfDefaultRipple))
1256 return terNO_RIPPLE;
1257
1258 return tesSUCCESS;
1259}
1260
1261[[nodiscard]] TER
1262canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
1263{
1264 auto mptID = mptIssue.getMptID();
1265 auto issuance = view.read(keylet::mptIssuance(mptID));
1266 if (!issuance)
1267 return tecOBJECT_NOT_FOUND;
1268 if (!issuance->isFlag(lsfMPTCanTransfer))
1269 return tecNO_AUTH;
1270
1271 return tesSUCCESS;
1272}
1273
1274[[nodiscard]] TER
1275canAddHolding(ReadView const& view, Asset const& asset)
1276{
1277 return std::visit(
1278 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
1279 return canAddHolding(view, issue);
1280 },
1281 asset.value());
1282}
1283
1284[[nodiscard]] TER
1285checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
1286{
1287 if (toSle == nullptr)
1288 return tecNO_DST;
1289
1290 // The tag is basically account-specific information we don't
1291 // understand, but we can require someone to fill it in.
1292 if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
1293 return tecDST_TAG_NEEDED; // Cannot send without a tag
1294
1295 return tesSUCCESS;
1296}
1297
1298/*
1299 * Checks if a withdrawal amount into the destination account exceeds
1300 * any applicable receiving limit.
1301 * Called by VaultWithdraw and LoanBrokerCoverWithdraw.
1302 *
1303 * IOU : Performs the trustline check against the destination account's
1304 * credit limit to ensure the account's trust maximum is not exceeded.
1305 *
1306 * MPT: The limit check is effectively skipped (returns true). This is
1307 * because MPT MaximumAmount relates to token supply, and withdrawal does not
1308 * involve minting new tokens that could exceed the global cap.
1309 * On withdrawal, tokens are simply transferred from the vault's pseudo-account
1310 * to the destination account. Since no new MPT tokens are minted during this
1311 * transfer, the withdrawal cannot violate the MPT MaximumAmount/supply cap
1312 * even if `from` is the issuer.
1313 */
1314static TER
1316 ReadView const& view,
1317 AccountID const& from,
1318 AccountID const& to,
1319 STAmount const& amount)
1320{
1321 auto const& issuer = amount.getIssuer();
1322 if (from == to || to == issuer || isXRP(issuer))
1323 return tesSUCCESS;
1324
1325 return std::visit(
1326 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
1327 if constexpr (std::is_same_v<TIss, Issue>)
1328 {
1329 auto const& currency = issue.currency;
1330 auto const owed = creditBalance(view, to, issuer, currency);
1331 if (owed <= beast::zero)
1332 {
1333 auto const limit = creditLimit(view, to, issuer, currency);
1334 if (-owed >= limit || amount > (limit + owed))
1335 return tecNO_LINE;
1336 }
1337 }
1338 return tesSUCCESS;
1339 },
1340 amount.asset().value());
1341}
1342
1343[[nodiscard]] TER
1345 ReadView const& view,
1346 AccountID const& from,
1347 AccountID const& to,
1348 SLE::const_ref toSle,
1349 STAmount const& amount,
1350 bool hasDestinationTag)
1351{
1352 if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
1353 return ret;
1354
1355 if (from == to)
1356 return tesSUCCESS;
1357
1358 if (toSle->isFlag(lsfDepositAuth))
1359 {
1360 if (!view.exists(keylet::depositPreauth(to, from)))
1361 return tecNO_PERMISSION;
1362 }
1363
1364 return withdrawToDestExceedsLimit(view, from, to, amount);
1365}
1366
1367[[nodiscard]] TER
1369 ReadView const& view,
1370 AccountID const& from,
1371 AccountID const& to,
1372 STAmount const& amount,
1373 bool hasDestinationTag)
1374{
1375 auto const toSle = view.read(keylet::account(to));
1376
1377 return canWithdraw(view, from, to, toSle, amount, hasDestinationTag);
1378}
1379
1380[[nodiscard]] TER
1381canWithdraw(ReadView const& view, STTx const& tx)
1382{
1383 auto const from = tx[sfAccount];
1384 auto const to = tx[~sfDestination].value_or(from);
1385
1386 return canWithdraw(
1387 view, from, to, tx[sfAmount], tx.isFieldPresent(sfDestinationTag));
1388}
1389
1390TER
1392 ApplyView& view,
1393 STTx const& tx,
1394 AccountID const& senderAcct,
1395 AccountID const& dstAcct,
1396 AccountID const& sourceAcct,
1397 XRPAmount priorBalance,
1398 STAmount const& amount,
1400{
1401 // Create trust line or MPToken for the receiving account
1402 if (dstAcct == senderAcct)
1403 {
1404 if (auto const ter = addEmptyHolding(
1405 view, senderAcct, priorBalance, amount.asset(), j);
1406 !isTesSuccess(ter) && ter != tecDUPLICATE)
1407 return ter;
1408 }
1409 else
1410 {
1411 auto dstSle = view.peek(keylet::account(dstAcct));
1412 if (auto err =
1413 verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
1414 return err;
1415 }
1416
1417 // Sanity check
1418 if (accountHolds(
1419 view,
1420 sourceAcct,
1421 amount.asset(),
1424 j) < amount)
1425 {
1426 // LCOV_EXCL_START
1427 JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "
1428 "broker cover assets.";
1429 return tefINTERNAL;
1430 // LCOV_EXCL_STOP
1431 }
1432
1433 // Move the funds directly from the broker's pseudo-account to the
1434 // dstAcct
1435 return accountSend(
1436 view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
1437}
1438
1439[[nodiscard]] TER
1441 ApplyView& view,
1442 AccountID const& accountID,
1443 XRPAmount priorBalance,
1444 Issue const& issue,
1445 beast::Journal journal)
1446{
1447 // Every account can hold XRP. An issuer can issue directly.
1448 if (issue.native() || accountID == issue.getIssuer())
1449 return tesSUCCESS;
1450
1451 auto const& issuerId = issue.getIssuer();
1452 auto const& currency = issue.currency;
1453 if (isGlobalFrozen(view, issuerId))
1454 return tecFROZEN; // LCOV_EXCL_LINE
1455
1456 auto const& srcId = issuerId;
1457 auto const& dstId = accountID;
1458 auto const high = srcId > dstId;
1459 auto const index = keylet::line(srcId, dstId, currency);
1460 auto const sleSrc = view.peek(keylet::account(srcId));
1461 auto const sleDst = view.peek(keylet::account(dstId));
1462 if (!sleDst || !sleSrc)
1463 return tefINTERNAL; // LCOV_EXCL_LINE
1464 if (!sleSrc->isFlag(lsfDefaultRipple))
1465 return tecINTERNAL; // LCOV_EXCL_LINE
1466 // If the line already exists, don't create it again.
1467 if (view.read(index))
1468 return tecDUPLICATE;
1469
1470 // Can the account cover the trust line reserve ?
1471 std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
1472 if (priorBalance < view.fees().accountReserve(ownerCount + 1))
1474
1475 return trustCreate(
1476 view,
1477 high,
1478 srcId,
1479 dstId,
1480 index.key,
1481 sleDst,
1482 /*auth=*/false,
1483 /*noRipple=*/true,
1484 /*freeze=*/false,
1485 /*deepFreeze*/ false,
1486 /*balance=*/STAmount{Issue{currency, noAccount()}},
1487 /*limit=*/STAmount{Issue{currency, dstId}},
1488 /*qualityIn=*/0,
1489 /*qualityOut=*/0,
1490 journal);
1491}
1492
1493[[nodiscard]] TER
1495 ApplyView& view,
1496 AccountID const& accountID,
1497 XRPAmount priorBalance,
1498 MPTIssue const& mptIssue,
1499 beast::Journal journal)
1500{
1501 auto const& mptID = mptIssue.getMptID();
1502 auto const mpt = view.peek(keylet::mptIssuance(mptID));
1503 if (!mpt)
1504 return tefINTERNAL; // LCOV_EXCL_LINE
1505 if (mpt->isFlag(lsfMPTLocked))
1506 return tefINTERNAL; // LCOV_EXCL_LINE
1507 if (view.peek(keylet::mptoken(mptID, accountID)))
1508 return tecDUPLICATE;
1509 if (accountID == mptIssue.getIssuer())
1510 return tesSUCCESS;
1511
1512 return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
1513}
1514
1515[[nodiscard]] TER
1517 ApplyView& view,
1518 XRPAmount const& priorBalance,
1519 MPTID const& mptIssuanceID,
1520 AccountID const& account,
1521 beast::Journal journal,
1522 std::uint32_t flags,
1523 std::optional<AccountID> holderID)
1524{
1525 auto const sleAcct = view.peek(keylet::account(account));
1526 if (!sleAcct)
1527 return tecINTERNAL; // LCOV_EXCL_LINE
1528
1529 // If the account that submitted the tx is a holder
1530 // Note: `account_` is holder's account
1531 // `holderID` is NOT used
1532 if (!holderID)
1533 {
1534 // When a holder wants to unauthorize/delete a MPT, the ledger must
1535 // - delete mptokenKey from owner directory
1536 // - delete the MPToken
1537 if (flags & tfMPTUnauthorize)
1538 {
1539 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1540 auto const sleMpt = view.peek(mptokenKey);
1541 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
1542 return tecINTERNAL; // LCOV_EXCL_LINE
1543
1544 if (!view.dirRemove(
1545 keylet::ownerDir(account),
1546 (*sleMpt)[sfOwnerNode],
1547 sleMpt->key(),
1548 false))
1549 return tecINTERNAL; // LCOV_EXCL_LINE
1550
1551 adjustOwnerCount(view, sleAcct, -1, journal);
1552
1553 view.erase(sleMpt);
1554 return tesSUCCESS;
1555 }
1556
1557 // A potential holder wants to authorize/hold a mpt, the ledger must:
1558 // - add the new mptokenKey to the owner directory
1559 // - create the MPToken object for the holder
1560
1561 // The reserve that is required to create the MPToken. Note
1562 // that although the reserve increases with every item
1563 // an account owns, in the case of MPTokens we only
1564 // *enforce* a reserve if the user owns more than two
1565 // items. This is similar to the reserve requirements of trust lines.
1566 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
1567 XRPAmount const reserveCreate(
1568 (uOwnerCount < 2) ? XRPAmount(beast::zero)
1569 : view.fees().accountReserve(uOwnerCount + 1));
1570
1571 if (priorBalance < reserveCreate)
1573
1574 // Defensive check before we attempt to create MPToken for the issuer
1575 auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
1576 if (!mpt || mpt->getAccountID(sfIssuer) == account)
1577 {
1578 // LCOV_EXCL_START
1579 UNREACHABLE(
1580 "xrpl::authorizeMPToken : invalid issuance or issuers token");
1581 if (view.rules().enabled(featureLendingProtocol))
1582 return tecINTERNAL;
1583 // LCOV_EXCL_STOP
1584 }
1585
1586 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1587 auto mptoken = std::make_shared<SLE>(mptokenKey);
1588 if (auto ter = dirLink(view, account, mptoken))
1589 return ter; // LCOV_EXCL_LINE
1590
1591 (*mptoken)[sfAccount] = account;
1592 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
1593 (*mptoken)[sfFlags] = 0;
1594 view.insert(mptoken);
1595
1596 // Update owner count.
1597 adjustOwnerCount(view, sleAcct, 1, journal);
1598
1599 return tesSUCCESS;
1600 }
1601
1602 auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
1603 if (!sleMptIssuance)
1604 return tecINTERNAL; // LCOV_EXCL_LINE
1605
1606 // If the account that submitted this tx is the issuer of the MPT
1607 // Note: `account_` is issuer's account
1608 // `holderID` is holder's account
1609 if (account != (*sleMptIssuance)[sfIssuer])
1610 return tecINTERNAL; // LCOV_EXCL_LINE
1611
1612 auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
1613 if (!sleMpt)
1614 return tecINTERNAL; // LCOV_EXCL_LINE
1615
1616 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
1617 std::uint32_t flagsOut = flagsIn;
1618
1619 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
1620 // their MPToken
1621 if (flags & tfMPTUnauthorize)
1622 flagsOut &= ~lsfMPTAuthorized;
1623 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
1624 // MPToken
1625 else
1626 flagsOut |= lsfMPTAuthorized;
1627
1628 if (flagsIn != flagsOut)
1629 sleMpt->setFieldU32(sfFlags, flagsOut);
1630
1631 view.update(sleMpt);
1632 return tesSUCCESS;
1633}
1634
1635TER
1637 ApplyView& view,
1638 bool const bSrcHigh,
1639 AccountID const& uSrcAccountID,
1640 AccountID const& uDstAccountID,
1641 uint256 const& uIndex, // --> ripple state entry
1642 SLE::ref sleAccount, // --> the account being set.
1643 bool const bAuth, // --> authorize account.
1644 bool const bNoRipple, // --> others cannot ripple through
1645 bool const bFreeze, // --> funds cannot leave
1646 bool bDeepFreeze, // --> can neither receive nor send funds
1647 STAmount const& saBalance, // --> balance of account being set.
1648 // Issuer should be noAccount()
1649 STAmount const& saLimit, // --> limit for account being set.
1650 // Issuer should be the account being set.
1651 std::uint32_t uQualityIn,
1652 std::uint32_t uQualityOut,
1654{
1655 JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
1656 << to_string(uDstAccountID) << ", "
1657 << saBalance.getFullText();
1658
1659 auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
1660 auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
1661 if (uLowAccountID == uHighAccountID)
1662 {
1663 // LCOV_EXCL_START
1664 UNREACHABLE("xrpl::trustCreate : trust line to self");
1665 if (view.rules().enabled(featureLendingProtocol))
1666 return tecINTERNAL;
1667 // LCOV_EXCL_STOP
1668 }
1669
1670 auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
1671 view.insert(sleRippleState);
1672
1673 auto lowNode = view.dirInsert(
1674 keylet::ownerDir(uLowAccountID),
1675 sleRippleState->key(),
1676 describeOwnerDir(uLowAccountID));
1677
1678 if (!lowNode)
1679 return tecDIR_FULL; // LCOV_EXCL_LINE
1680
1681 auto highNode = view.dirInsert(
1682 keylet::ownerDir(uHighAccountID),
1683 sleRippleState->key(),
1684 describeOwnerDir(uHighAccountID));
1685
1686 if (!highNode)
1687 return tecDIR_FULL; // LCOV_EXCL_LINE
1688
1689 bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
1690 bool const bSetHigh = bSrcHigh ^ bSetDst;
1691
1692 XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
1693 if (!sleAccount)
1694 return tefINTERNAL; // LCOV_EXCL_LINE
1695
1696 XRPL_ASSERT(
1697 sleAccount->getAccountID(sfAccount) ==
1698 (bSetHigh ? uHighAccountID : uLowAccountID),
1699 "xrpl::trustCreate : matching account ID");
1700 auto const slePeer =
1701 view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
1702 if (!slePeer)
1703 return tecNO_TARGET;
1704
1705 // Remember deletion hints.
1706 sleRippleState->setFieldU64(sfLowNode, *lowNode);
1707 sleRippleState->setFieldU64(sfHighNode, *highNode);
1708
1709 sleRippleState->setFieldAmount(
1710 bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
1711 sleRippleState->setFieldAmount(
1712 bSetHigh ? sfLowLimit : sfHighLimit,
1714 saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
1715
1716 if (uQualityIn)
1717 sleRippleState->setFieldU32(
1718 bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
1719
1720 if (uQualityOut)
1721 sleRippleState->setFieldU32(
1722 bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
1723
1724 std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
1725
1726 if (bAuth)
1727 {
1728 uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
1729 }
1730 if (bNoRipple)
1731 {
1732 uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
1733 }
1734 if (bFreeze)
1735 {
1736 uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
1737 }
1738 if (bDeepFreeze)
1739 {
1740 uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
1741 }
1742
1743 if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
1744 {
1745 // The other side's default is no rippling
1746 uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
1747 }
1748
1749 sleRippleState->setFieldU32(sfFlags, uFlags);
1750 adjustOwnerCount(view, sleAccount, 1, j);
1751
1752 // ONLY: Create ripple balance.
1753 sleRippleState->setFieldAmount(
1754 sfBalance, bSetHigh ? -saBalance : saBalance);
1755
1756 view.creditHook(
1757 uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
1758
1759 return tesSUCCESS;
1760}
1761
1762[[nodiscard]] TER
1764 ApplyView& view,
1765 AccountID const& accountID,
1766 Issue const& issue,
1767 beast::Journal journal)
1768{
1769 if (issue.native())
1770 {
1771 auto const sle = view.read(keylet::account(accountID));
1772 if (!sle)
1773 return tecINTERNAL; // LCOV_EXCL_LINE
1774
1775 auto const balance = sle->getFieldAmount(sfBalance);
1776 if (balance.xrp() != 0)
1777 return tecHAS_OBLIGATIONS;
1778
1779 return tesSUCCESS;
1780 }
1781
1782 // `asset` is an IOU.
1783 // If the account is the issuer, then no line should exist. Check anyway. If
1784 // a line does exist, it will get deleted. If not, return success.
1785 bool const accountIsIssuer = accountID == issue.account;
1786 auto const line = view.peek(keylet::line(accountID, issue));
1787 if (!line)
1788 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
1789 if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
1790 return tecHAS_OBLIGATIONS;
1791
1792 // Adjust the owner count(s)
1793 if (line->isFlag(lsfLowReserve))
1794 {
1795 // Clear reserve for low account.
1796 auto sleLowAccount =
1797 view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
1798 if (!sleLowAccount)
1799 return tecINTERNAL; // LCOV_EXCL_LINE
1800
1801 adjustOwnerCount(view, sleLowAccount, -1, journal);
1802 // It's not really necessary to clear the reserve flag, since the line
1803 // is about to be deleted, but this will make the metadata reflect an
1804 // accurate state at the time of deletion.
1805 line->clearFlag(lsfLowReserve);
1806 }
1807
1808 if (line->isFlag(lsfHighReserve))
1809 {
1810 // Clear reserve for high account.
1811 auto sleHighAccount =
1812 view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
1813 if (!sleHighAccount)
1814 return tecINTERNAL; // LCOV_EXCL_LINE
1815
1816 adjustOwnerCount(view, sleHighAccount, -1, journal);
1817 // It's not really necessary to clear the reserve flag, since the line
1818 // is about to be deleted, but this will make the metadata reflect an
1819 // accurate state at the time of deletion.
1820 line->clearFlag(lsfHighReserve);
1821 }
1822
1823 return trustDelete(
1824 view,
1825 line,
1826 line->at(sfLowLimit)->getIssuer(),
1827 line->at(sfHighLimit)->getIssuer(),
1828 journal);
1829}
1830
1831[[nodiscard]] TER
1833 ApplyView& view,
1834 AccountID const& accountID,
1835 MPTIssue const& mptIssue,
1836 beast::Journal journal)
1837{
1838 // If the account is the issuer, then no token should exist. MPTs do not
1839 // have the legacy ability to create such a situation, but check anyway. If
1840 // a token does exist, it will get deleted. If not, return success.
1841 bool const accountIsIssuer = accountID == mptIssue.getIssuer();
1842 auto const& mptID = mptIssue.getMptID();
1843 auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
1844 if (!mptoken)
1845 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
1846 // Unlike a trust line, if the account is the issuer, and the token has a
1847 // balance, it can not just be deleted, because that will throw the issuance
1848 // accounting out of balance, so fail. Since this should be impossible
1849 // anyway, I'm not going to put any effort into it.
1850 if (mptoken->at(sfMPTAmount) != 0)
1851 return tecHAS_OBLIGATIONS;
1852
1853 return authorizeMPToken(
1854 view,
1855 {}, // priorBalance
1856 mptID,
1857 accountID,
1858 journal,
1859 tfMPTUnauthorize // flags
1860 );
1861}
1862
1863TER
1865 ApplyView& view,
1866 std::shared_ptr<SLE> const& sleRippleState,
1867 AccountID const& uLowAccountID,
1868 AccountID const& uHighAccountID,
1870{
1871 // Detect legacy dirs.
1872 std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
1873 std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
1874
1875 JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
1876
1877 if (!view.dirRemove(
1878 keylet::ownerDir(uLowAccountID),
1879 uLowNode,
1880 sleRippleState->key(),
1881 false))
1882 {
1883 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1884 }
1885
1886 JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
1887
1888 if (!view.dirRemove(
1889 keylet::ownerDir(uHighAccountID),
1890 uHighNode,
1891 sleRippleState->key(),
1892 false))
1893 {
1894 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1895 }
1896
1897 JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
1898 view.erase(sleRippleState);
1899
1900 return tesSUCCESS;
1901}
1902
1903TER
1905{
1906 if (!sle)
1907 return tesSUCCESS;
1908 auto offerIndex = sle->key();
1909 auto owner = sle->getAccountID(sfAccount);
1910
1911 // Detect legacy directories.
1912 uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
1913
1914 if (!view.dirRemove(
1915 keylet::ownerDir(owner),
1916 sle->getFieldU64(sfOwnerNode),
1917 offerIndex,
1918 false))
1919 {
1920 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1921 }
1922
1923 if (!view.dirRemove(
1924 keylet::page(uDirectory),
1925 sle->getFieldU64(sfBookNode),
1926 offerIndex,
1927 false))
1928 {
1929 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1930 }
1931
1932 if (sle->isFieldPresent(sfAdditionalBooks))
1933 {
1934 XRPL_ASSERT(
1935 sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
1936 "xrpl::offerDelete : should be a hybrid domain offer");
1937
1938 auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
1939
1940 for (auto const& bookDir : additionalBookDirs)
1941 {
1942 auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
1943 auto const& dirNode = bookDir.getFieldU64(sfBookNode);
1944
1945 if (!view.dirRemove(
1946 keylet::page(dirIndex), dirNode, offerIndex, false))
1947 {
1948 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1949 }
1950 }
1951 }
1952
1953 adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
1954
1955 view.erase(sle);
1956
1957 return tesSUCCESS;
1958}
1959
1960// Direct send w/o fees:
1961// - Redeeming IOUs and/or sending sender's own IOUs.
1962// - Create trust line if needed.
1963// --> bCheckIssuer : normally require issuer to be involved.
1964static TER
1966 ApplyView& view,
1967 AccountID const& uSenderID,
1968 AccountID const& uReceiverID,
1969 STAmount const& saAmount,
1970 bool bCheckIssuer,
1972{
1973 AccountID const& issuer = saAmount.getIssuer();
1974 Currency const& currency = saAmount.getCurrency();
1975
1976 // Make sure issuer is involved.
1977 XRPL_ASSERT(
1978 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
1979 "xrpl::rippleCreditIOU : matching issuer or don't care");
1980 (void)issuer;
1981
1982 // Disallow sending to self.
1983 XRPL_ASSERT(
1984 uSenderID != uReceiverID,
1985 "xrpl::rippleCreditIOU : sender is not receiver");
1986
1987 bool const bSenderHigh = uSenderID > uReceiverID;
1988 auto const index = keylet::line(uSenderID, uReceiverID, currency);
1989
1990 XRPL_ASSERT(
1991 !isXRP(uSenderID) && uSenderID != noAccount(),
1992 "xrpl::rippleCreditIOU : sender is not XRP");
1993 XRPL_ASSERT(
1994 !isXRP(uReceiverID) && uReceiverID != noAccount(),
1995 "xrpl::rippleCreditIOU : receiver is not XRP");
1996
1997 // If the line exists, modify it accordingly.
1998 if (auto const sleRippleState = view.peek(index))
1999 {
2000 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
2001
2002 if (bSenderHigh)
2003 saBalance.negate(); // Put balance in sender terms.
2004
2005 view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
2006
2007 STAmount const saBefore = saBalance;
2008
2009 saBalance -= saAmount;
2010
2011 JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
2012 << to_string(uReceiverID)
2013 << " : before=" << saBefore.getFullText()
2014 << " amount=" << saAmount.getFullText()
2015 << " after=" << saBalance.getFullText();
2016
2017 std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
2018 bool bDelete = false;
2019
2020 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
2021 // for anyone to understand.
2022 if (saBefore > beast::zero
2023 // Sender balance was positive.
2024 && saBalance <= beast::zero
2025 // Sender is zero or negative.
2026 && (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
2027 // Sender reserve is set.
2028 &&
2029 static_cast<bool>(
2030 uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
2031 static_cast<bool>(
2032 view.read(keylet::account(uSenderID))->getFlags() &
2034 !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
2035 !sleRippleState->getFieldAmount(
2036 !bSenderHigh ? sfLowLimit : sfHighLimit)
2037 // Sender trust limit is 0.
2038 && !sleRippleState->getFieldU32(
2039 !bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
2040 // Sender quality in is 0.
2041 && !sleRippleState->getFieldU32(
2042 !bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
2043 // Sender quality out is 0.
2044 {
2045 // Clear the reserve of the sender, possibly delete the line!
2047 view, view.peek(keylet::account(uSenderID)), -1, j);
2048
2049 // Clear reserve flag.
2050 sleRippleState->setFieldU32(
2051 sfFlags,
2052 uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
2053
2054 // Balance is zero, receiver reserve is clear.
2055 bDelete = !saBalance // Balance is zero.
2056 && !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
2057 // Receiver reserve is clear.
2058 }
2059
2060 if (bSenderHigh)
2061 saBalance.negate();
2062
2063 // Want to reflect balance to zero even if we are deleting line.
2064 sleRippleState->setFieldAmount(sfBalance, saBalance);
2065 // ONLY: Adjust ripple balance.
2066
2067 if (bDelete)
2068 {
2069 return trustDelete(
2070 view,
2071 sleRippleState,
2072 bSenderHigh ? uReceiverID : uSenderID,
2073 !bSenderHigh ? uReceiverID : uSenderID,
2074 j);
2075 }
2076
2077 view.update(sleRippleState);
2078 return tesSUCCESS;
2079 }
2080
2081 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
2082 STAmount saBalance{saAmount};
2083
2084 saBalance.setIssuer(noAccount());
2085
2086 JLOG(j.debug()) << "rippleCreditIOU: "
2087 "create line: "
2088 << to_string(uSenderID) << " -> " << to_string(uReceiverID)
2089 << " : " << saAmount.getFullText();
2090
2091 auto const sleAccount = view.peek(keylet::account(uReceiverID));
2092 if (!sleAccount)
2093 return tefINTERNAL; // LCOV_EXCL_LINE
2094
2095 bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
2096
2097 return trustCreate(
2098 view,
2099 bSenderHigh,
2100 uSenderID,
2101 uReceiverID,
2102 index.key,
2103 sleAccount,
2104 false,
2105 noRipple,
2106 false,
2107 false,
2108 saBalance,
2109 saReceiverLimit,
2110 0,
2111 0,
2112 j);
2113}
2114
2115// Send regardless of limits.
2116// --> saAmount: Amount/currency/issuer to deliver to receiver.
2117// <-- saActual: Amount actually cost. Sender pays fees.
2118static TER
2120 ApplyView& view,
2121 AccountID const& uSenderID,
2122 AccountID const& uReceiverID,
2123 STAmount const& saAmount,
2124 STAmount& saActual,
2126 WaiveTransferFee waiveFee)
2127{
2128 auto const& issuer = saAmount.getIssuer();
2129
2130 XRPL_ASSERT(
2131 !isXRP(uSenderID) && !isXRP(uReceiverID),
2132 "xrpl::rippleSendIOU : neither sender nor receiver is XRP");
2133 XRPL_ASSERT(
2134 uSenderID != uReceiverID,
2135 "xrpl::rippleSendIOU : sender is not receiver");
2136
2137 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
2138 {
2139 // Direct send: redeeming IOUs and/or sending own IOUs.
2140 auto const ter =
2141 rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
2142 if (ter != tesSUCCESS)
2143 return ter;
2144 saActual = saAmount;
2145 return tesSUCCESS;
2146 }
2147
2148 // Sending 3rd party IOUs: transit.
2149
2150 // Calculate the amount to transfer accounting
2151 // for any transfer fees if the fee is not waived:
2152 saActual = (waiveFee == WaiveTransferFee::Yes)
2153 ? saAmount
2154 : multiply(saAmount, transferRate(view, issuer));
2155
2156 JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
2157 << to_string(uReceiverID)
2158 << " : deliver=" << saAmount.getFullText()
2159 << " cost=" << saActual.getFullText();
2160
2161 TER terResult =
2162 rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
2163
2164 if (tesSUCCESS == terResult)
2165 terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
2166
2167 return terResult;
2168}
2169
2170// Send regardless of limits.
2171// --> receivers: Amount/currency/issuer to deliver to receivers.
2172// <-- saActual: Amount actually cost to sender. Sender pays fees.
2173static TER
2175 ApplyView& view,
2176 AccountID const& senderID,
2177 Issue const& issue,
2178 MultiplePaymentDestinations const& receivers,
2179 STAmount& actual,
2181 WaiveTransferFee waiveFee)
2182{
2183 auto const& issuer = issue.getIssuer();
2184
2185 XRPL_ASSERT(
2186 !isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP");
2187
2188 // These may diverge
2189 STAmount takeFromSender{issue};
2190 actual = takeFromSender;
2191
2192 // Failures return immediately.
2193 for (auto const& r : receivers)
2194 {
2195 auto const& receiverID = r.first;
2196 STAmount amount{issue, r.second};
2197
2198 /* If we aren't sending anything or if the sender is the same as the
2199 * receiver then we don't need to do anything.
2200 */
2201 if (!amount || (senderID == receiverID))
2202 continue;
2203
2204 XRPL_ASSERT(
2205 !isXRP(receiverID),
2206 "xrpl::rippleSendMultiIOU : receiver is not XRP");
2207
2208 if (senderID == issuer || receiverID == issuer || issuer == noAccount())
2209 {
2210 // Direct send: redeeming IOUs and/or sending own IOUs.
2211 if (auto const ter = rippleCreditIOU(
2212 view, senderID, receiverID, amount, false, j))
2213 return ter;
2214 actual += amount;
2215 // Do not add amount to takeFromSender, because rippleCreditIOU took
2216 // it.
2217
2218 continue;
2219 }
2220
2221 // Sending 3rd party IOUs: transit.
2222
2223 // Calculate the amount to transfer accounting
2224 // for any transfer fees if the fee is not waived:
2225 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
2226 ? amount
2227 : multiply(amount, transferRate(view, issuer));
2228 actual += actualSend;
2229 takeFromSender += actualSend;
2230
2231 JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID)
2232 << " - > " << to_string(receiverID)
2233 << " : deliver=" << amount.getFullText()
2234 << " cost=" << actual.getFullText();
2235
2236 if (TER const terResult =
2237 rippleCreditIOU(view, issuer, receiverID, amount, true, j))
2238 return terResult;
2239 }
2240
2241 if (senderID != issuer && takeFromSender)
2242 {
2243 if (TER const terResult = rippleCreditIOU(
2244 view, senderID, issuer, takeFromSender, true, j))
2245 return terResult;
2246 }
2247
2248 return tesSUCCESS;
2249}
2250
2251static TER
2253 ApplyView& view,
2254 AccountID const& uSenderID,
2255 AccountID const& uReceiverID,
2256 STAmount const& saAmount,
2258 WaiveTransferFee waiveFee)
2259{
2260 if (view.rules().enabled(fixAMMv1_1))
2261 {
2262 if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
2263 {
2264 return tecINTERNAL; // LCOV_EXCL_LINE
2265 }
2266 }
2267 else
2268 {
2269 // LCOV_EXCL_START
2270 XRPL_ASSERT(
2271 saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
2272 "xrpl::accountSendIOU : minimum amount and not MPT");
2273 // LCOV_EXCL_STOP
2274 }
2275
2276 /* If we aren't sending anything or if the sender is the same as the
2277 * receiver then we don't need to do anything.
2278 */
2279 if (!saAmount || (uSenderID == uReceiverID))
2280 return tesSUCCESS;
2281
2282 if (!saAmount.native())
2283 {
2284 STAmount saActual;
2285
2286 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
2287 << to_string(uReceiverID) << " : "
2288 << saAmount.getFullText();
2289
2290 return rippleSendIOU(
2291 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
2292 }
2293
2294 /* XRP send which does not check reserve and can do pure adjustment.
2295 * Note that sender or receiver may be null and this not a mistake; this
2296 * setup is used during pathfinding and it is carefully controlled to
2297 * ensure that transfers are balanced.
2298 */
2299 TER terResult(tesSUCCESS);
2300
2301 SLE::pointer sender = uSenderID != beast::zero
2302 ? view.peek(keylet::account(uSenderID))
2303 : SLE::pointer();
2304 SLE::pointer receiver = uReceiverID != beast::zero
2305 ? view.peek(keylet::account(uReceiverID))
2306 : SLE::pointer();
2307
2308 if (auto stream = j.trace())
2309 {
2310 std::string sender_bal("-");
2311 std::string receiver_bal("-");
2312
2313 if (sender)
2314 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2315
2316 if (receiver)
2317 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
2318
2319 stream << "accountSendIOU> " << to_string(uSenderID) << " ("
2320 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
2321 << receiver_bal << ") : " << saAmount.getFullText();
2322 }
2323
2324 if (sender)
2325 {
2326 if (sender->getFieldAmount(sfBalance) < saAmount)
2327 {
2328 // VFALCO Its laborious to have to mutate the
2329 // TER based on params everywhere
2330 // LCOV_EXCL_START
2331 terResult = view.open() ? TER{telFAILED_PROCESSING}
2333 // LCOV_EXCL_STOP
2334 }
2335 else
2336 {
2337 auto const sndBal = sender->getFieldAmount(sfBalance);
2338 view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
2339
2340 // Decrement XRP balance.
2341 sender->setFieldAmount(sfBalance, sndBal - saAmount);
2342 view.update(sender);
2343 }
2344 }
2345
2346 if (tesSUCCESS == terResult && receiver)
2347 {
2348 // Increment XRP balance.
2349 auto const rcvBal = receiver->getFieldAmount(sfBalance);
2350 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
2351 view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
2352
2353 view.update(receiver);
2354 }
2355
2356 if (auto stream = j.trace())
2357 {
2358 std::string sender_bal("-");
2359 std::string receiver_bal("-");
2360
2361 if (sender)
2362 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2363
2364 if (receiver)
2365 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
2366
2367 stream << "accountSendIOU< " << to_string(uSenderID) << " ("
2368 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
2369 << receiver_bal << ") : " << saAmount.getFullText();
2370 }
2371
2372 return terResult;
2373}
2374
2375static TER
2377 ApplyView& view,
2378 AccountID const& senderID,
2379 Issue const& issue,
2380 MultiplePaymentDestinations const& receivers,
2382 WaiveTransferFee waiveFee)
2383{
2384 XRPL_ASSERT_PARTS(
2385 receivers.size() > 1,
2386 "xrpl::accountSendMultiIOU",
2387 "multiple recipients provided");
2388
2389 if (!issue.native())
2390 {
2391 STAmount actual;
2392 JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID)
2393 << " sending " << receivers.size() << " IOUs";
2394
2395 return rippleSendMultiIOU(
2396 view, senderID, issue, receivers, actual, j, waiveFee);
2397 }
2398
2399 /* XRP send which does not check reserve and can do pure adjustment.
2400 * Note that sender or receiver may be null and this not a mistake; this
2401 * setup could be used during pathfinding and it is carefully controlled to
2402 * ensure that transfers are balanced.
2403 */
2404
2405 SLE::pointer sender = senderID != beast::zero
2406 ? view.peek(keylet::account(senderID))
2407 : SLE::pointer();
2408
2409 if (auto stream = j.trace())
2410 {
2411 std::string sender_bal("-");
2412
2413 if (sender)
2414 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2415
2416 stream << "accountSendMultiIOU> " << to_string(senderID) << " ("
2417 << sender_bal << ") -> " << receivers.size() << " receivers.";
2418 }
2419
2420 // Failures return immediately.
2421 STAmount takeFromSender{issue};
2422 for (auto const& r : receivers)
2423 {
2424 auto const& receiverID = r.first;
2425 STAmount amount{issue, r.second};
2426
2427 if (amount < beast::zero)
2428 {
2429 return tecINTERNAL; // LCOV_EXCL_LINE
2430 }
2431
2432 /* If we aren't sending anything or if the sender is the same as the
2433 * receiver then we don't need to do anything.
2434 */
2435 if (!amount || (senderID == receiverID))
2436 continue;
2437
2438 SLE::pointer receiver = receiverID != beast::zero
2439 ? view.peek(keylet::account(receiverID))
2440 : SLE::pointer();
2441
2442 if (auto stream = j.trace())
2443 {
2444 std::string receiver_bal("-");
2445
2446 if (receiver)
2447 receiver_bal =
2448 receiver->getFieldAmount(sfBalance).getFullText();
2449
2450 stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
2451 << to_string(receiverID) << " (" << receiver_bal
2452 << ") : " << amount.getFullText();
2453 }
2454
2455 if (receiver)
2456 {
2457 // Increment XRP balance.
2458 auto const rcvBal = receiver->getFieldAmount(sfBalance);
2459 receiver->setFieldAmount(sfBalance, rcvBal + amount);
2460 view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
2461
2462 view.update(receiver);
2463
2464 // Take what is actually sent
2465 takeFromSender += amount;
2466 }
2467
2468 if (auto stream = j.trace())
2469 {
2470 std::string receiver_bal("-");
2471
2472 if (receiver)
2473 receiver_bal =
2474 receiver->getFieldAmount(sfBalance).getFullText();
2475
2476 stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
2477 << to_string(receiverID) << " (" << receiver_bal
2478 << ") : " << amount.getFullText();
2479 }
2480 }
2481
2482 if (sender)
2483 {
2484 if (sender->getFieldAmount(sfBalance) < takeFromSender)
2485 {
2486 return TER{tecFAILED_PROCESSING};
2487 }
2488 else
2489 {
2490 auto const sndBal = sender->getFieldAmount(sfBalance);
2491 view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
2492
2493 // Decrement XRP balance.
2494 sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
2495 view.update(sender);
2496 }
2497 }
2498
2499 if (auto stream = j.trace())
2500 {
2501 std::string sender_bal("-");
2502 std::string receiver_bal("-");
2503
2504 if (sender)
2505 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2506
2507 stream << "accountSendMultiIOU< " << to_string(senderID) << " ("
2508 << sender_bal << ") -> " << receivers.size() << " receivers.";
2509 }
2510 return tesSUCCESS;
2511}
2512
2513static TER
2515 ApplyView& view,
2516 AccountID const& uSenderID,
2517 AccountID const& uReceiverID,
2518 STAmount const& saAmount,
2520{
2521 // Do not check MPT authorization here - it must have been checked earlier
2522 auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
2523 auto const& issuer = saAmount.getIssuer();
2524 auto sleIssuance = view.peek(mptID);
2525 if (!sleIssuance)
2526 return tecOBJECT_NOT_FOUND;
2527 if (uSenderID == issuer)
2528 {
2529 (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
2530 view.update(sleIssuance);
2531 }
2532 else
2533 {
2534 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
2535 if (auto sle = view.peek(mptokenID))
2536 {
2537 auto const amt = sle->getFieldU64(sfMPTAmount);
2538 auto const pay = saAmount.mpt().value();
2539 if (amt < pay)
2540 return tecINSUFFICIENT_FUNDS;
2541 (*sle)[sfMPTAmount] = amt - pay;
2542 view.update(sle);
2543 }
2544 else
2545 return tecNO_AUTH;
2546 }
2547
2548 if (uReceiverID == issuer)
2549 {
2550 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
2551 auto const redeem = saAmount.mpt().value();
2552 if (outstanding >= redeem)
2553 {
2554 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
2555 view.update(sleIssuance);
2556 }
2557 else
2558 return tecINTERNAL; // LCOV_EXCL_LINE
2559 }
2560 else
2561 {
2562 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
2563 if (auto sle = view.peek(mptokenID))
2564 {
2565 (*sle)[sfMPTAmount] += saAmount.mpt().value();
2566 view.update(sle);
2567 }
2568 else
2569 return tecNO_AUTH;
2570 }
2571
2572 return tesSUCCESS;
2573}
2574
2575static TER
2577 ApplyView& view,
2578 AccountID const& uSenderID,
2579 AccountID const& uReceiverID,
2580 STAmount const& saAmount,
2581 STAmount& saActual,
2583 WaiveTransferFee waiveFee)
2584{
2585 XRPL_ASSERT(
2586 uSenderID != uReceiverID,
2587 "xrpl::rippleSendMPT : sender is not receiver");
2588
2589 // Safe to get MPT since rippleSendMPT is only called by accountSendMPT
2590 auto const& issuer = saAmount.getIssuer();
2591
2592 auto const sle =
2593 view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
2594 if (!sle)
2595 return tecOBJECT_NOT_FOUND;
2596
2597 if (uSenderID == issuer || uReceiverID == issuer)
2598 {
2599 // if sender is issuer, check that the new OutstandingAmount will not
2600 // exceed MaximumAmount
2601 if (uSenderID == issuer)
2602 {
2603 auto const sendAmount = saAmount.mpt().value();
2604 auto const maximumAmount =
2605 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
2606 if (sendAmount > maximumAmount ||
2607 sle->getFieldU64(sfOutstandingAmount) >
2608 maximumAmount - sendAmount)
2609 return tecPATH_DRY;
2610 }
2611
2612 // Direct send: redeeming MPTs and/or sending own MPTs.
2613 auto const ter =
2614 rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
2615 if (ter != tesSUCCESS)
2616 return ter;
2617 saActual = saAmount;
2618 return tesSUCCESS;
2619 }
2620
2621 // Sending 3rd party MPTs: transit.
2622 saActual = (waiveFee == WaiveTransferFee::Yes)
2623 ? saAmount
2624 : multiply(
2625 saAmount,
2626 transferRate(view, saAmount.get<MPTIssue>().getMptID()));
2627
2628 JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
2629 << to_string(uReceiverID)
2630 << " : deliver=" << saAmount.getFullText()
2631 << " cost=" << saActual.getFullText();
2632
2633 if (auto const terResult =
2634 rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
2635 terResult != tesSUCCESS)
2636 return terResult;
2637
2638 return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
2639}
2640
2641static TER
2643 ApplyView& view,
2644 AccountID const& senderID,
2645 MPTIssue const& mptIssue,
2646 MultiplePaymentDestinations const& receivers,
2647 STAmount& actual,
2649 WaiveTransferFee waiveFee)
2650{
2651 // Safe to get MPT since rippleSendMultiMPT is only called by
2652 // accountSendMultiMPT
2653 auto const& issuer = mptIssue.getIssuer();
2654
2655 auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
2656 if (!sle)
2657 return tecOBJECT_NOT_FOUND;
2658
2659 // These may diverge
2660 STAmount takeFromSender{mptIssue};
2661 actual = takeFromSender;
2662
2663 for (auto const& r : receivers)
2664 {
2665 auto const& receiverID = r.first;
2666 STAmount amount{mptIssue, r.second};
2667
2668 if (amount < beast::zero)
2669 {
2670 return tecINTERNAL; // LCOV_EXCL_LINE
2671 }
2672
2673 /* If we aren't sending anything or if the sender is the same as the
2674 * receiver then we don't need to do anything.
2675 */
2676 if (!amount || (senderID == receiverID))
2677 continue;
2678
2679 if (senderID == issuer || receiverID == issuer)
2680 {
2681 // if sender is issuer, check that the new OutstandingAmount will
2682 // not exceed MaximumAmount
2683 if (senderID == issuer)
2684 {
2685 XRPL_ASSERT_PARTS(
2686 takeFromSender == beast::zero,
2687 "rippler::rippleSendMultiMPT",
2688 "sender == issuer, takeFromSender == zero");
2689 auto const sendAmount = amount.mpt().value();
2690 auto const maximumAmount =
2691 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
2692 if (sendAmount > maximumAmount ||
2693 sle->getFieldU64(sfOutstandingAmount) >
2694 maximumAmount - sendAmount)
2695 return tecPATH_DRY;
2696 }
2697
2698 // Direct send: redeeming MPTs and/or sending own MPTs.
2699 if (auto const ter =
2700 rippleCreditMPT(view, senderID, receiverID, amount, j))
2701 return ter;
2702 actual += amount;
2703 // Do not add amount to takeFromSender, because rippleCreditMPT took
2704 // it
2705
2706 continue;
2707 }
2708
2709 // Sending 3rd party MPTs: transit.
2710 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
2711 ? amount
2712 : multiply(
2713 amount,
2714 transferRate(view, amount.get<MPTIssue>().getMptID()));
2715 actual += actualSend;
2716 takeFromSender += actualSend;
2717
2718 JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID)
2719 << " - > " << to_string(receiverID)
2720 << " : deliver=" << amount.getFullText()
2721 << " cost=" << actualSend.getFullText();
2722
2723 if (auto const terResult =
2724 rippleCreditMPT(view, issuer, receiverID, amount, j))
2725 return terResult;
2726 }
2727 if (senderID != issuer && takeFromSender)
2728 {
2729 if (TER const terResult =
2730 rippleCreditMPT(view, senderID, issuer, takeFromSender, j))
2731 return terResult;
2732 }
2733
2734 return tesSUCCESS;
2735}
2736
2737static TER
2739 ApplyView& view,
2740 AccountID const& uSenderID,
2741 AccountID const& uReceiverID,
2742 STAmount const& saAmount,
2744 WaiveTransferFee waiveFee)
2745{
2746 XRPL_ASSERT(
2747 saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
2748 "xrpl::accountSendMPT : minimum amount and MPT");
2749
2750 /* If we aren't sending anything or if the sender is the same as the
2751 * receiver then we don't need to do anything.
2752 */
2753 if (!saAmount || (uSenderID == uReceiverID))
2754 return tesSUCCESS;
2755
2756 STAmount saActual{saAmount.asset()};
2757
2758 return rippleSendMPT(
2759 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
2760}
2761
2762static TER
2764 ApplyView& view,
2765 AccountID const& senderID,
2766 MPTIssue const& mptIssue,
2767 MultiplePaymentDestinations const& receivers,
2769 WaiveTransferFee waiveFee)
2770{
2771 STAmount actual;
2772
2773 return rippleSendMultiMPT(
2774 view, senderID, mptIssue, receivers, actual, j, waiveFee);
2775}
2776
2777TER
2779 ApplyView& view,
2780 AccountID const& uSenderID,
2781 AccountID const& uReceiverID,
2782 STAmount const& saAmount,
2784 WaiveTransferFee waiveFee)
2785{
2786 return std::visit(
2787 [&]<ValidIssueType TIss>(TIss const& issue) {
2788 if constexpr (std::is_same_v<TIss, Issue>)
2789 return accountSendIOU(
2790 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2791 else
2792 return accountSendMPT(
2793 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2794 },
2795 saAmount.asset().value());
2796}
2797
2798TER
2800 ApplyView& view,
2801 AccountID const& senderID,
2802 Asset const& asset,
2803 MultiplePaymentDestinations const& receivers,
2805 WaiveTransferFee waiveFee)
2806{
2807 XRPL_ASSERT_PARTS(
2808 receivers.size() > 1,
2809 "xrpl::accountSendMulti",
2810 "multiple recipients provided");
2811 return std::visit(
2812 [&]<ValidIssueType TIss>(TIss const& issue) {
2813 if constexpr (std::is_same_v<TIss, Issue>)
2814 return accountSendMultiIOU(
2815 view, senderID, issue, receivers, j, waiveFee);
2816 else
2817 return accountSendMultiMPT(
2818 view, senderID, issue, receivers, j, waiveFee);
2819 },
2820 asset.value());
2821}
2822
2823static bool
2825 ApplyView& view,
2826 SLE::pointer state,
2827 bool bSenderHigh,
2828 AccountID const& sender,
2829 STAmount const& before,
2830 STAmount const& after,
2832{
2833 if (!state)
2834 return false;
2835 std::uint32_t const flags(state->getFieldU32(sfFlags));
2836
2837 auto sle = view.peek(keylet::account(sender));
2838 if (!sle)
2839 return false;
2840
2841 // YYY Could skip this if rippling in reverse.
2842 if (before > beast::zero
2843 // Sender balance was positive.
2844 && after <= beast::zero
2845 // Sender is zero or negative.
2846 && (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
2847 // Sender reserve is set.
2848 && static_cast<bool>(
2849 flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
2850 static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
2851 !(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
2852 !state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
2853 // Sender trust limit is 0.
2854 && !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
2855 // Sender quality in is 0.
2856 &&
2857 !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
2858 // Sender quality out is 0.
2859 {
2860 // VFALCO Where is the line being deleted?
2861 // Clear the reserve of the sender, possibly delete the line!
2862 adjustOwnerCount(view, sle, -1, j);
2863
2864 // Clear reserve flag.
2865 state->setFieldU32(
2866 sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
2867
2868 // Balance is zero, receiver reserve is clear.
2869 if (!after // Balance is zero.
2870 && !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
2871 return true;
2872 }
2873 return false;
2874}
2875
2876TER
2878 ApplyView& view,
2879 AccountID const& account,
2880 STAmount const& amount,
2881 Issue const& issue,
2883{
2884 XRPL_ASSERT(
2885 !isXRP(account) && !isXRP(issue.account),
2886 "xrpl::issueIOU : neither account nor issuer is XRP");
2887
2888 // Consistency check
2889 XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
2890
2891 // Can't send to self!
2892 XRPL_ASSERT(
2893 issue.account != account, "xrpl::issueIOU : not issuer account");
2894
2895 JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": "
2896 << amount.getFullText();
2897
2898 bool bSenderHigh = issue.account > account;
2899
2900 auto const index = keylet::line(issue.account, account, issue.currency);
2901
2902 if (auto state = view.peek(index))
2903 {
2904 STAmount final_balance = state->getFieldAmount(sfBalance);
2905
2906 if (bSenderHigh)
2907 final_balance.negate(); // Put balance in sender terms.
2908
2909 STAmount const start_balance = final_balance;
2910
2911 final_balance -= amount;
2912
2913 auto const must_delete = updateTrustLine(
2914 view,
2915 state,
2916 bSenderHigh,
2917 issue.account,
2918 start_balance,
2919 final_balance,
2920 j);
2921
2922 view.creditHook(issue.account, account, amount, start_balance);
2923
2924 if (bSenderHigh)
2925 final_balance.negate();
2926
2927 // Adjust the balance on the trust line if necessary. We do this even if
2928 // we are going to delete the line to reflect the correct balance at the
2929 // time of deletion.
2930 state->setFieldAmount(sfBalance, final_balance);
2931 if (must_delete)
2932 return trustDelete(
2933 view,
2934 state,
2935 bSenderHigh ? account : issue.account,
2936 bSenderHigh ? issue.account : account,
2937 j);
2938
2939 view.update(state);
2940
2941 return tesSUCCESS;
2942 }
2943
2944 // NIKB TODO: The limit uses the receiver's account as the issuer and
2945 // this is unnecessarily inefficient as copying which could be avoided
2946 // is now required. Consider available options.
2947 STAmount const limit(Issue{issue.currency, account});
2948 STAmount final_balance = amount;
2949
2950 final_balance.setIssuer(noAccount());
2951
2952 auto const receiverAccount = view.peek(keylet::account(account));
2953 if (!receiverAccount)
2954 return tefINTERNAL; // LCOV_EXCL_LINE
2955
2956 bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
2957
2958 return trustCreate(
2959 view,
2960 bSenderHigh,
2961 issue.account,
2962 account,
2963 index.key,
2964 receiverAccount,
2965 false,
2966 noRipple,
2967 false,
2968 false,
2969 final_balance,
2970 limit,
2971 0,
2972 0,
2973 j);
2974}
2975
2976TER
2978 ApplyView& view,
2979 AccountID const& account,
2980 STAmount const& amount,
2981 Issue const& issue,
2983{
2984 XRPL_ASSERT(
2985 !isXRP(account) && !isXRP(issue.account),
2986 "xrpl::redeemIOU : neither account nor issuer is XRP");
2987
2988 // Consistency check
2989 XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
2990
2991 // Can't send to self!
2992 XRPL_ASSERT(
2993 issue.account != account, "xrpl::redeemIOU : not issuer account");
2994
2995 JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": "
2996 << amount.getFullText();
2997
2998 bool bSenderHigh = account > issue.account;
2999
3000 if (auto state =
3001 view.peek(keylet::line(account, issue.account, issue.currency)))
3002 {
3003 STAmount final_balance = state->getFieldAmount(sfBalance);
3004
3005 if (bSenderHigh)
3006 final_balance.negate(); // Put balance in sender terms.
3007
3008 STAmount const start_balance = final_balance;
3009
3010 final_balance -= amount;
3011
3012 auto const must_delete = updateTrustLine(
3013 view, state, bSenderHigh, account, start_balance, final_balance, j);
3014
3015 view.creditHook(account, issue.account, amount, start_balance);
3016
3017 if (bSenderHigh)
3018 final_balance.negate();
3019
3020 // Adjust the balance on the trust line if necessary. We do this even if
3021 // we are going to delete the line to reflect the correct balance at the
3022 // time of deletion.
3023 state->setFieldAmount(sfBalance, final_balance);
3024
3025 if (must_delete)
3026 {
3027 return trustDelete(
3028 view,
3029 state,
3030 bSenderHigh ? issue.account : account,
3031 bSenderHigh ? account : issue.account,
3032 j);
3033 }
3034
3035 view.update(state);
3036 return tesSUCCESS;
3037 }
3038
3039 // In order to hold an IOU, a trust line *MUST* exist to track the
3040 // balance. If it doesn't, then something is very wrong. Don't try
3041 // to continue.
3042 // LCOV_EXCL_START
3043 JLOG(j.fatal()) << "redeemIOU: " << to_string(account)
3044 << " attempts to redeem " << amount.getFullText()
3045 << " but no trust line exists!";
3046
3047 return tefINTERNAL;
3048 // LCOV_EXCL_STOP
3049}
3050
3051TER
3053 ApplyView& view,
3054 AccountID const& from,
3055 AccountID const& to,
3056 STAmount const& amount,
3058{
3059 XRPL_ASSERT(
3060 from != beast::zero, "xrpl::transferXRP : nonzero from account");
3061 XRPL_ASSERT(to != beast::zero, "xrpl::transferXRP : nonzero to account");
3062 XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
3063 XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
3064
3065 SLE::pointer const sender = view.peek(keylet::account(from));
3066 SLE::pointer const receiver = view.peek(keylet::account(to));
3067 if (!sender || !receiver)
3068 return tefINTERNAL; // LCOV_EXCL_LINE
3069
3070 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> "
3071 << to_string(to) << ") : " << amount.getFullText();
3072
3073 if (sender->getFieldAmount(sfBalance) < amount)
3074 {
3075 // VFALCO Its unfortunate we have to keep
3076 // mutating these TER everywhere
3077 // FIXME: this logic should be moved to callers maybe?
3078 // LCOV_EXCL_START
3079 return view.open() ? TER{telFAILED_PROCESSING}
3081 // LCOV_EXCL_STOP
3082 }
3083
3084 // Decrement XRP balance.
3085 sender->setFieldAmount(
3086 sfBalance, sender->getFieldAmount(sfBalance) - amount);
3087 view.update(sender);
3088
3089 receiver->setFieldAmount(
3090 sfBalance, receiver->getFieldAmount(sfBalance) + amount);
3091 view.update(receiver);
3092
3093 return tesSUCCESS;
3094}
3095
3096TER
3098 ReadView const& view,
3099 Issue const& issue,
3100 AccountID const& account,
3101 AuthType authType)
3102{
3103 if (isXRP(issue) || issue.account == account)
3104 return tesSUCCESS;
3105
3106 auto const trustLine =
3107 view.read(keylet::line(account, issue.account, issue.currency));
3108 // If account has no line, and this is a strong check, fail
3109 if (!trustLine && authType == AuthType::StrongAuth)
3110 return tecNO_LINE;
3111
3112 // If this is a weak or legacy check, or if the account has a line, fail if
3113 // auth is required and not set on the line
3114 if (auto const issuerAccount = view.read(keylet::account(issue.account));
3115 issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
3116 {
3117 if (trustLine)
3118 return ((*trustLine)[sfFlags] &
3119 ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
3120 ? tesSUCCESS
3121 : TER{tecNO_AUTH};
3122 return TER{tecNO_LINE};
3123 }
3124
3125 return tesSUCCESS;
3126}
3127
3128TER
3130 ReadView const& view,
3131 MPTIssue const& mptIssue,
3132 AccountID const& account,
3133 AuthType authType,
3134 int depth)
3135{
3136 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3137 auto const sleIssuance = view.read(mptID);
3138 if (!sleIssuance)
3139 return tecOBJECT_NOT_FOUND;
3140
3141 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
3142
3143 // issuer is always "authorized"
3144 if (mptIssuer == account) // Issuer won't have MPToken
3145 return tesSUCCESS;
3146
3147 bool const featureSAVEnabled =
3148 view.rules().enabled(featureSingleAssetVault);
3149
3150 if (featureSAVEnabled)
3151 {
3152 if (depth >= maxAssetCheckDepth)
3153 return tecINTERNAL; // LCOV_EXCL_LINE
3154
3155 // requireAuth is recursive if the issuer is a vault pseudo-account
3156 auto const sleIssuer = view.read(keylet::account(mptIssuer));
3157 if (!sleIssuer)
3158 return tefINTERNAL; // LCOV_EXCL_LINE
3159
3160 if (sleIssuer->isFieldPresent(sfVaultID))
3161 {
3162 auto const sleVault =
3163 view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
3164 if (!sleVault)
3165 return tefINTERNAL; // LCOV_EXCL_LINE
3166
3167 auto const asset = sleVault->at(sfAsset);
3168 if (auto const err = std::visit(
3169 [&]<ValidIssueType TIss>(TIss const& issue) {
3170 if constexpr (std::is_same_v<TIss, Issue>)
3171 return requireAuth(view, issue, account, authType);
3172 else
3173 return requireAuth(
3174 view, issue, account, authType, depth + 1);
3175 },
3176 asset.value());
3177 !isTesSuccess(err))
3178 return err;
3179 }
3180 }
3181
3182 auto const mptokenID = keylet::mptoken(mptID.key, account);
3183 auto const sleToken = view.read(mptokenID);
3184
3185 // if account has no MPToken, fail
3186 if (!sleToken &&
3187 (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
3188 return tecNO_AUTH;
3189
3190 // Note, this check is not amendment-gated because DomainID will be always
3191 // empty **unless** writing to it has been enabled by an amendment
3192 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
3193 if (maybeDomainID)
3194 {
3195 XRPL_ASSERT(
3196 sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
3197 "xrpl::requireAuth : issuance requires authorization");
3198 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
3199 if (auto const ter =
3200 credentials::validDomain(view, *maybeDomainID, account);
3201 isTesSuccess(ter))
3202 return ter; // Note: sleToken might be null
3203 else if (!sleToken)
3204 return ter;
3205 // We ignore error from validDomain if we found sleToken, as it could
3206 // belong to someone who is explicitly authorized e.g. a vault owner.
3207 }
3208
3209 if (featureSAVEnabled)
3210 {
3211 // Implicitly authorize Vault and LoanBroker pseudo-accounts
3212 if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
3213 return tesSUCCESS;
3214 }
3215
3216 // mptoken must be authorized if issuance enabled requireAuth
3217 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
3218 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
3219 return tecNO_AUTH;
3220
3221 return tesSUCCESS; // Note: sleToken might be null
3222}
3223
3224[[nodiscard]] TER
3226 ApplyView& view,
3227 MPTID const& mptIssuanceID,
3228 AccountID const& account,
3229 XRPAmount const& priorBalance, // for MPToken authorization
3231{
3232 auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
3233 if (!sleIssuance)
3234 return tefINTERNAL; // LCOV_EXCL_LINE
3235
3236 XRPL_ASSERT(
3237 sleIssuance->isFlag(lsfMPTRequireAuth),
3238 "xrpl::enforceMPTokenAuthorization : authorization required");
3239
3240 if (account == sleIssuance->at(sfIssuer))
3241 return tefINTERNAL; // LCOV_EXCL_LINE
3242
3243 auto const keylet = keylet::mptoken(mptIssuanceID, account);
3244 auto const sleToken = view.read(keylet); // NOTE: might be null
3245 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
3246 bool expired = false;
3247 bool const authorizedByDomain = [&]() -> bool {
3248 // NOTE: defensive here, should be checked in preclaim
3249 if (!maybeDomainID.has_value())
3250 return false; // LCOV_EXCL_LINE
3251
3252 auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
3253 if (isTesSuccess(ter))
3254 return true;
3255 if (ter == tecEXPIRED)
3256 expired = true;
3257 return false;
3258 }();
3259
3260 if (!authorizedByDomain && sleToken == nullptr)
3261 {
3262 // Could not find MPToken and won't create one, could be either of:
3263 //
3264 // 1. Field sfDomainID not set in MPTokenIssuance or
3265 // 2. Account has no matching and accepted credentials or
3266 // 3. Account has all expired credentials (deleted in verifyValidDomain)
3267 //
3268 // Either way, return tecNO_AUTH and there is nothing else to do
3269 return expired ? tecEXPIRED : tecNO_AUTH;
3270 }
3271 else if (!authorizedByDomain && maybeDomainID.has_value())
3272 {
3273 // Found an MPToken but the account is not authorized and we expect
3274 // it to have been authorized by the domain. This could be because the
3275 // credentials used to create the MPToken have expired or been deleted.
3276 return expired ? tecEXPIRED : tecNO_AUTH;
3277 }
3278 else if (!authorizedByDomain)
3279 {
3280 // We found an MPToken, but sfDomainID is not set, so this is a classic
3281 // MPToken which requires authorization by the token issuer.
3282 XRPL_ASSERT(
3283 sleToken != nullptr && !maybeDomainID.has_value(),
3284 "xrpl::enforceMPTokenAuthorization : found MPToken");
3285 if (sleToken->isFlag(lsfMPTAuthorized))
3286 return tesSUCCESS;
3287
3288 return tecNO_AUTH;
3289 }
3290 else if (authorizedByDomain && sleToken != nullptr)
3291 {
3292 // Found an MPToken, authorized by the domain. Ignore authorization flag
3293 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
3294 XRPL_ASSERT(
3295 maybeDomainID.has_value(),
3296 "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
3297 return tesSUCCESS;
3298 }
3299 else if (authorizedByDomain)
3300 {
3301 // Could not find MPToken but there should be one because we are
3302 // authorized by domain. Proceed to create it, then return tesSUCCESS
3303 XRPL_ASSERT(
3304 maybeDomainID.has_value() && sleToken == nullptr,
3305 "xrpl::enforceMPTokenAuthorization : new MPToken for domain");
3306 if (auto const err = authorizeMPToken(
3307 view,
3308 priorBalance, // priorBalance
3309 mptIssuanceID, // mptIssuanceID
3310 account, // account
3311 j);
3312 !isTesSuccess(err))
3313 return err;
3314
3315 return tesSUCCESS;
3316 }
3317
3318 // LCOV_EXCL_START
3319 UNREACHABLE(
3320 "xrpl::enforceMPTokenAuthorization : condition list is incomplete");
3321 return tefINTERNAL;
3322 // LCOV_EXCL_STOP
3323}
3324
3325TER
3327 ReadView const& view,
3328 MPTIssue const& mptIssue,
3329 AccountID const& from,
3330 AccountID const& to)
3331{
3332 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3333 auto const sleIssuance = view.read(mptID);
3334 if (!sleIssuance)
3335 return tecOBJECT_NOT_FOUND;
3336
3337 if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
3338 {
3339 if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
3340 return TER{tecNO_AUTH};
3341 }
3342 return tesSUCCESS;
3343}
3344
3345[[nodiscard]] TER
3347 ReadView const& view,
3348 Issue const& issue,
3349 AccountID const& from,
3350 AccountID const& to)
3351{
3352 if (issue.native())
3353 return tesSUCCESS;
3354
3355 auto const& issuerId = issue.getIssuer();
3356 if (issuerId == from || issuerId == to)
3357 return tesSUCCESS;
3358 auto const sleIssuer = view.read(keylet::account(issuerId));
3359 if (sleIssuer == nullptr)
3360 return tefINTERNAL; // LCOV_EXCL_LINE
3361
3362 auto const isRippleDisabled = [&](AccountID account) -> bool {
3363 // Line might not exist, but some transfers can create it. If this
3364 // is the case, just check the default ripple on the issuer account.
3365 auto const line = view.read(keylet::line(account, issue));
3366 if (line)
3367 {
3368 bool const issuerHigh = issuerId > account;
3369 return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
3370 }
3371 return sleIssuer->isFlag(lsfDefaultRipple) == false;
3372 };
3373
3374 // Fail if rippling disabled on both trust lines
3375 if (isRippleDisabled(from) && isRippleDisabled(to))
3376 return terNO_RIPPLE;
3377
3378 return tesSUCCESS;
3379}
3380
3381TER
3383 ApplyView& view,
3384 Keylet const& ownerDirKeylet,
3385 EntryDeleter const& deleter,
3387 std::optional<uint16_t> maxNodesToDelete)
3388{
3389 // Delete all the entries in the account directory.
3390 std::shared_ptr<SLE> sleDirNode{};
3391 unsigned int uDirEntry{0};
3392 uint256 dirEntry{beast::zero};
3393 std::uint32_t deleted = 0;
3394
3395 if (view.exists(ownerDirKeylet) &&
3396 dirFirst(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
3397 {
3398 do
3399 {
3400 if (maxNodesToDelete && ++deleted > *maxNodesToDelete)
3401 return tecINCOMPLETE;
3402
3403 // Choose the right way to delete each directory node.
3404 auto sleItem = view.peek(keylet::child(dirEntry));
3405 if (!sleItem)
3406 {
3407 // Directory node has an invalid index. Bail out.
3408 // LCOV_EXCL_START
3409 JLOG(j.fatal())
3410 << "DeleteAccount: Directory node in ledger " << view.seq()
3411 << " has index to object that is missing: "
3412 << to_string(dirEntry);
3413 return tefBAD_LEDGER;
3414 // LCOV_EXCL_STOP
3415 }
3416
3417 LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
3418 sleItem->getFieldU16(sfLedgerEntryType))};
3419
3420 // Deleter handles the details of specific account-owned object
3421 // deletion
3422 auto const [ter, skipEntry] = deleter(nodeType, dirEntry, sleItem);
3423 if (ter != tesSUCCESS)
3424 return ter;
3425
3426 // dirFirst() and dirNext() are like iterators with exposed
3427 // internal state. We'll take advantage of that exposed state
3428 // to solve a common C++ problem: iterator invalidation while
3429 // deleting elements from a container.
3430 //
3431 // We have just deleted one directory entry, which means our
3432 // "iterator state" is invalid.
3433 //
3434 // 1. During the process of getting an entry from the
3435 // directory uDirEntry was incremented from 'it' to 'it'+1.
3436 //
3437 // 2. We then deleted the entry at index 'it', which means the
3438 // entry that was at 'it'+1 has now moved to 'it'.
3439 //
3440 // 3. So we verify that uDirEntry is indeed 'it'+1. Then we jam it
3441 // back to 'it' to "un-invalidate" the iterator.
3442 XRPL_ASSERT(
3443 uDirEntry >= 1,
3444 "xrpl::cleanupOnAccountDelete : minimum dir entries");
3445 if (uDirEntry == 0)
3446 {
3447 // LCOV_EXCL_START
3448 JLOG(j.error())
3449 << "DeleteAccount iterator re-validation failed.";
3450 return tefBAD_LEDGER;
3451 // LCOV_EXCL_STOP
3452 }
3453 if (skipEntry == SkipEntry::No)
3454 uDirEntry--;
3455
3456 } while (
3457 dirNext(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
3458 }
3459
3460 return tesSUCCESS;
3461}
3462
3463TER
3465 ApplyView& view,
3466 std::shared_ptr<SLE> sleState,
3467 std::optional<AccountID> const& ammAccountID,
3469{
3470 if (!sleState || sleState->getType() != ltRIPPLE_STATE)
3471 return tecINTERNAL; // LCOV_EXCL_LINE
3472
3473 auto const& [low, high] = std::minmax(
3474 sleState->getFieldAmount(sfLowLimit).getIssuer(),
3475 sleState->getFieldAmount(sfHighLimit).getIssuer());
3476 auto sleLow = view.peek(keylet::account(low));
3477 auto sleHigh = view.peek(keylet::account(high));
3478 if (!sleLow || !sleHigh)
3479 return tecINTERNAL; // LCOV_EXCL_LINE
3480
3481 bool const ammLow = sleLow->isFieldPresent(sfAMMID);
3482 bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
3483
3484 // can't both be AMM
3485 if (ammLow && ammHigh)
3486 return tecINTERNAL; // LCOV_EXCL_LINE
3487
3488 // at least one must be
3489 if (!ammLow && !ammHigh)
3490 return terNO_AMM;
3491
3492 // one must be the target amm
3493 if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
3494 return terNO_AMM;
3495
3496 if (auto const ter = trustDelete(view, sleState, low, high, j);
3497 ter != tesSUCCESS)
3498 {
3499 JLOG(j.error())
3500 << "deleteAMMTrustLine: failed to delete the trustline.";
3501 return ter;
3502 }
3503
3504 auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
3505 if (!(sleState->getFlags() & uFlags))
3506 return tecINTERNAL; // LCOV_EXCL_LINE
3507
3508 adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
3509
3510 return tesSUCCESS;
3511}
3512
3513TER
3515 ApplyView& view,
3516 AccountID const& uSenderID,
3517 AccountID const& uReceiverID,
3518 STAmount const& saAmount,
3519 bool bCheckIssuer,
3521{
3522 return std::visit(
3523 [&]<ValidIssueType TIss>(TIss const& issue) {
3524 if constexpr (std::is_same_v<TIss, Issue>)
3525 {
3526 return rippleCreditIOU(
3527 view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
3528 }
3529 else
3530 {
3531 XRPL_ASSERT(
3532 !bCheckIssuer, "xrpl::rippleCredit : not checking issuer");
3533 return rippleCreditMPT(
3534 view, uSenderID, uReceiverID, saAmount, j);
3535 }
3536 },
3537 saAmount.asset().value());
3538}
3539
3540[[nodiscard]] std::optional<STAmount>
3542 std::shared_ptr<SLE const> const& vault,
3543 std::shared_ptr<SLE const> const& issuance,
3544 STAmount const& assets)
3545{
3546 XRPL_ASSERT(
3547 !assets.negative(),
3548 "xrpl::assetsToSharesDeposit : non-negative assets");
3549 XRPL_ASSERT(
3550 assets.asset() == vault->at(sfAsset),
3551 "xrpl::assetsToSharesDeposit : assets and vault match");
3552 if (assets.negative() || assets.asset() != vault->at(sfAsset))
3553 return std::nullopt; // LCOV_EXCL_LINE
3554
3555 Number const assetTotal = vault->at(sfAssetsTotal);
3556 STAmount shares{vault->at(sfShareMPTID)};
3557 if (assetTotal == 0)
3558 return STAmount{
3559 shares.asset(),
3560 Number(assets.mantissa(), assets.exponent() + vault->at(sfScale))
3561 .truncate()};
3562
3563 Number const shareTotal = issuance->at(sfOutstandingAmount);
3564 shares = ((shareTotal * assets) / assetTotal).truncate();
3565 return shares;
3566}
3567
3568[[nodiscard]] std::optional<STAmount>
3570 std::shared_ptr<SLE const> const& vault,
3571 std::shared_ptr<SLE const> const& issuance,
3572 STAmount const& shares)
3573{
3574 XRPL_ASSERT(
3575 !shares.negative(),
3576 "xrpl::sharesToAssetsDeposit : non-negative shares");
3577 XRPL_ASSERT(
3578 shares.asset() == vault->at(sfShareMPTID),
3579 "xrpl::sharesToAssetsDeposit : shares and vault match");
3580 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
3581 return std::nullopt; // LCOV_EXCL_LINE
3582
3583 Number const assetTotal = vault->at(sfAssetsTotal);
3584 STAmount assets{vault->at(sfAsset)};
3585 if (assetTotal == 0)
3586 return STAmount{
3587 assets.asset(),
3588 shares.mantissa(),
3589 shares.exponent() - vault->at(sfScale),
3590 false};
3591
3592 Number const shareTotal = issuance->at(sfOutstandingAmount);
3593 assets = (assetTotal * shares) / shareTotal;
3594 return assets;
3595}
3596
3597[[nodiscard]] std::optional<STAmount>
3599 std::shared_ptr<SLE const> const& vault,
3600 std::shared_ptr<SLE const> const& issuance,
3601 STAmount const& assets,
3602 TruncateShares truncate)
3603{
3604 XRPL_ASSERT(
3605 !assets.negative(),
3606 "xrpl::assetsToSharesDeposit : non-negative assets");
3607 XRPL_ASSERT(
3608 assets.asset() == vault->at(sfAsset),
3609 "xrpl::assetsToSharesWithdraw : assets and vault match");
3610 if (assets.negative() || assets.asset() != vault->at(sfAsset))
3611 return std::nullopt; // LCOV_EXCL_LINE
3612
3613 Number assetTotal = vault->at(sfAssetsTotal);
3614 assetTotal -= vault->at(sfLossUnrealized);
3615 STAmount shares{vault->at(sfShareMPTID)};
3616 if (assetTotal == 0)
3617 return shares;
3618 Number const shareTotal = issuance->at(sfOutstandingAmount);
3619 Number result = (shareTotal * assets) / assetTotal;
3620 if (truncate == TruncateShares::yes)
3621 result = result.truncate();
3622 shares = result;
3623 return shares;
3624}
3625
3626[[nodiscard]] std::optional<STAmount>
3628 std::shared_ptr<SLE const> const& vault,
3629 std::shared_ptr<SLE const> const& issuance,
3630 STAmount const& shares)
3631{
3632 XRPL_ASSERT(
3633 !shares.negative(),
3634 "xrpl::sharesToAssetsDeposit : non-negative shares");
3635 XRPL_ASSERT(
3636 shares.asset() == vault->at(sfShareMPTID),
3637 "xrpl::sharesToAssetsWithdraw : shares and vault match");
3638 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
3639 return std::nullopt; // LCOV_EXCL_LINE
3640
3641 Number assetTotal = vault->at(sfAssetsTotal);
3642 assetTotal -= vault->at(sfLossUnrealized);
3643 STAmount assets{vault->at(sfAsset)};
3644 if (assetTotal == 0)
3645 return assets;
3646 Number const shareTotal = issuance->at(sfOutstandingAmount);
3647 assets = (assetTotal * shares) / shareTotal;
3648 return assets;
3649}
3650
3651TER
3653 ApplyView& view,
3654 AccountID const& sender,
3655 STAmount const& amount,
3657{
3658 auto const mptIssue = amount.get<MPTIssue>();
3659 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3660 auto sleIssuance = view.peek(mptID);
3661 if (!sleIssuance)
3662 { // LCOV_EXCL_START
3663 JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
3664 << mptIssue.getMptID();
3665 return tecOBJECT_NOT_FOUND;
3666 } // LCOV_EXCL_STOP
3667
3668 if (amount.getIssuer() == sender)
3669 { // LCOV_EXCL_START
3670 JLOG(j.error())
3671 << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
3672 return tecINTERNAL;
3673 } // LCOV_EXCL_STOP
3674
3675 // 1. Decrease the MPT Holder MPTAmount
3676 // 2. Increase the MPT Holder EscrowedAmount
3677 {
3678 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3679 auto sle = view.peek(mptokenID);
3680 if (!sle)
3681 { // LCOV_EXCL_START
3682 JLOG(j.error())
3683 << "rippleLockEscrowMPT: MPToken not found for " << sender;
3684 return tecOBJECT_NOT_FOUND;
3685 } // LCOV_EXCL_STOP
3686
3687 auto const amt = sle->getFieldU64(sfMPTAmount);
3688 auto const pay = amount.mpt().value();
3689
3690 // Underflow check for subtraction
3691 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
3692 { // LCOV_EXCL_START
3693 JLOG(j.error())
3694 << "rippleLockEscrowMPT: insufficient MPTAmount for "
3695 << to_string(sender) << ": " << amt << " < " << pay;
3696 return tecINTERNAL;
3697 } // LCOV_EXCL_STOP
3698
3699 (*sle)[sfMPTAmount] = amt - pay;
3700
3701 // Overflow check for addition
3702 uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
3703
3704 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
3705 { // LCOV_EXCL_START
3706 JLOG(j.error())
3707 << "rippleLockEscrowMPT: overflow on locked amount for "
3708 << to_string(sender) << ": " << locked << " + " << pay;
3709 return tecINTERNAL;
3710 } // LCOV_EXCL_STOP
3711
3712 if (sle->isFieldPresent(sfLockedAmount))
3713 (*sle)[sfLockedAmount] += pay;
3714 else
3715 sle->setFieldU64(sfLockedAmount, pay);
3716
3717 view.update(sle);
3718 }
3719
3720 // 1. Increase the Issuance EscrowedAmount
3721 // 2. DO NOT change the Issuance OutstandingAmount
3722 {
3723 uint64_t const issuanceEscrowed =
3724 (*sleIssuance)[~sfLockedAmount].value_or(0);
3725 auto const pay = amount.mpt().value();
3726
3727 // Overflow check for addition
3728 if (!canAdd(
3729 STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
3730 { // LCOV_EXCL_START
3731 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
3732 "locked amount for "
3733 << mptIssue.getMptID() << ": " << issuanceEscrowed
3734 << " + " << pay;
3735 return tecINTERNAL;
3736 } // LCOV_EXCL_STOP
3737
3738 if (sleIssuance->isFieldPresent(sfLockedAmount))
3739 (*sleIssuance)[sfLockedAmount] += pay;
3740 else
3741 sleIssuance->setFieldU64(sfLockedAmount, pay);
3742
3743 view.update(sleIssuance);
3744 }
3745 return tesSUCCESS;
3746}
3747
3748TER
3750 ApplyView& view,
3751 AccountID const& sender,
3752 AccountID const& receiver,
3753 STAmount const& netAmount,
3754 STAmount const& grossAmount,
3756{
3757 if (!view.rules().enabled(fixTokenEscrowV1))
3758 XRPL_ASSERT(
3759 netAmount == grossAmount,
3760 "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
3761
3762 auto const& issuer = netAmount.getIssuer();
3763 auto const& mptIssue = netAmount.get<MPTIssue>();
3764 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3765 auto sleIssuance = view.peek(mptID);
3766 if (!sleIssuance)
3767 { // LCOV_EXCL_START
3768 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
3769 << mptIssue.getMptID();
3770 return tecOBJECT_NOT_FOUND;
3771 } // LCOV_EXCL_STOP
3772
3773 // Decrease the Issuance EscrowedAmount
3774 {
3775 if (!sleIssuance->isFieldPresent(sfLockedAmount))
3776 { // LCOV_EXCL_START
3777 JLOG(j.error())
3778 << "rippleUnlockEscrowMPT: no locked amount in issuance for "
3779 << mptIssue.getMptID();
3780 return tecINTERNAL;
3781 } // LCOV_EXCL_STOP
3782
3783 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
3784 auto const redeem = grossAmount.mpt().value();
3785
3786 // Underflow check for subtraction
3787 if (!canSubtract(
3788 STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
3789 { // LCOV_EXCL_START
3790 JLOG(j.error())
3791 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3792 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
3793 return tecINTERNAL;
3794 } // LCOV_EXCL_STOP
3795
3796 auto const newLocked = locked - redeem;
3797 if (newLocked == 0)
3798 sleIssuance->makeFieldAbsent(sfLockedAmount);
3799 else
3800 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
3801 view.update(sleIssuance);
3802 }
3803
3804 if (issuer != receiver)
3805 {
3806 // Increase the MPT Holder MPTAmount
3807 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
3808 auto sle = view.peek(mptokenID);
3809 if (!sle)
3810 { // LCOV_EXCL_START
3811 JLOG(j.error())
3812 << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
3813 return tecOBJECT_NOT_FOUND;
3814 } // LCOV_EXCL_STOP
3815
3816 auto current = sle->getFieldU64(sfMPTAmount);
3817 auto delta = netAmount.mpt().value();
3818
3819 // Overflow check for addition
3820 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
3821 { // LCOV_EXCL_START
3822 JLOG(j.error())
3823 << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
3824 << to_string(receiver) << ": " << current << " + " << delta;
3825 return tecINTERNAL;
3826 } // LCOV_EXCL_STOP
3827
3828 (*sle)[sfMPTAmount] += delta;
3829 view.update(sle);
3830 }
3831 else
3832 {
3833 // Decrease the Issuance OutstandingAmount
3834 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3835 auto const redeem = netAmount.mpt().value();
3836
3837 // Underflow check for subtraction
3838 if (!canSubtract(
3839 STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
3840 { // LCOV_EXCL_START
3841 JLOG(j.error())
3842 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3843 << mptIssue.getMptID() << ": " << outstanding << " < "
3844 << redeem;
3845 return tecINTERNAL;
3846 } // LCOV_EXCL_STOP
3847
3848 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
3849 view.update(sleIssuance);
3850 }
3851
3852 if (issuer == sender)
3853 { // LCOV_EXCL_START
3854 JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
3855 "cannot unlock MPTs.";
3856 return tecINTERNAL;
3857 } // LCOV_EXCL_STOP
3858 else
3859 {
3860 // Decrease the MPT Holder EscrowedAmount
3861 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3862 auto sle = view.peek(mptokenID);
3863 if (!sle)
3864 { // LCOV_EXCL_START
3865 JLOG(j.error())
3866 << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
3867 return tecOBJECT_NOT_FOUND;
3868 } // LCOV_EXCL_STOP
3869
3870 if (!sle->isFieldPresent(sfLockedAmount))
3871 { // LCOV_EXCL_START
3872 JLOG(j.error())
3873 << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
3874 << to_string(sender);
3875 return tecINTERNAL;
3876 } // LCOV_EXCL_STOP
3877
3878 auto const locked = sle->getFieldU64(sfLockedAmount);
3879 auto const delta = grossAmount.mpt().value();
3880
3881 // Underflow check for subtraction
3882 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
3883 { // LCOV_EXCL_START
3884 JLOG(j.error())
3885 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3886 << to_string(sender) << ": " << locked << " < " << delta;
3887 return tecINTERNAL;
3888 } // LCOV_EXCL_STOP
3889
3890 auto const newLocked = locked - delta;
3891 if (newLocked == 0)
3892 sle->makeFieldAbsent(sfLockedAmount);
3893 else
3894 sle->setFieldU64(sfLockedAmount, newLocked);
3895 view.update(sle);
3896 }
3897
3898 // Note: The gross amount is the amount that was locked, the net
3899 // amount is the amount that is being unlocked. The difference is the fee
3900 // that was charged for the transfer. If this difference is greater than
3901 // zero, we need to update the outstanding amount.
3902 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
3903 if (diff != 0)
3904 {
3905 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3906 // Underflow check for subtraction
3907 if (!canSubtract(
3908 STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
3909 { // LCOV_EXCL_START
3910 JLOG(j.error())
3911 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3912 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
3913 return tecINTERNAL;
3914 } // LCOV_EXCL_STOP
3915
3916 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
3917 view.update(sleIssuance);
3918 }
3919 return tesSUCCESS;
3920}
3921
3922bool
3924{
3925 return now.time_since_epoch().count() > mark;
3926}
3927
3928} // namespace xrpl
Provide a light-weight way to check active() before string formatting.
Definition Journal.h:186
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:333
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
static Sink & getNullSink()
Returns a Sink which does nothing.
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
Stream warn() const
Definition Journal.h:321
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:124
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 void creditHook(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance)
Definition ApplyView.h:224
virtual void adjustOwnerCountHook(AccountID const &account, std::uint32_t cur, std::uint32_t next)
Definition ApplyView.h:235
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:300
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
constexpr value_type const & value() const
Definition Asset.h:157
A currency issued by an account.
Definition Issue.h:14
Currency currency
Definition Issue.h:16
AccountID account
Definition Issue.h:17
bool native() const
Definition Issue.cpp:47
AccountID const & getIssuer() const
Definition Issue.h:26
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
static LedgerFormats const & getInstance()
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:114
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:27
AccountID const & getIssuer() const
Definition MPTIssue.cpp:21
std::chrono::time_point< NetClock > time_point
Definition chrono.h:50
std::chrono::duration< rep, period > duration
Definition chrono.h:49
Number is a floating point type that can represent a wide range of values.
Definition Number.h:212
Number truncate() const noexcept
Definition Number.cpp:856
A view into a ledger.
Definition ReadView.h:32
virtual Rules const & rules() const =0
Returns the tx processing rules.
NetClock::time_point parentCloseTime() const
Returns the close time of the previous ledger.
Definition ReadView.h:92
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 LedgerHeader const & header() const =0
Returns information about the ledger.
virtual STAmount balanceHook(AccountID const &account, AccountID const &issuer, STAmount const &amount) const
Definition ReadView.h:159
virtual bool open() const =0
Returns true if this reflects an open ledger.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:99
virtual std::uint32_t ownerCountHook(AccountID const &account, std::uint32_t count) const
Definition ReadView.h:173
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:123
Identifies fields.
Definition SField.h:127
@ sMD_PseudoAccount
Definition SField.h:137
constexpr bool holds() const noexcept
Definition STAmount.h:470
constexpr TIss const & get() const
std::string getFullText() const override
Definition STAmount.cpp:678
static constexpr std::uint64_t cMaxValue
Definition STAmount.h:52
void setIssuer(AccountID const &uIssuer)
Definition STAmount.h:602
void negate()
Definition STAmount.h:578
std::uint64_t mantissa() const noexcept
Definition STAmount.h:482
bool negative() const noexcept
Definition STAmount.h:476
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:525
static int const cMaxOffset
Definition STAmount.h:47
Currency const & getCurrency() const
Definition STAmount.h:507
bool native() const noexcept
Definition STAmount.h:463
Asset const & asset() const
Definition STAmount.h:488
MPTAmount mpt() const
Definition STAmount.cpp:299
int exponent() const noexcept
Definition STAmount.h:451
AccountID const & getIssuer() const
Definition STAmount.h:513
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry const > const_pointer
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:465
std::size_t size() const
T count_if(T... args)
T emplace_back(T... args)
T is_same_v
T max(T... args)
T minmax(T... args)
bool internalDirFirst(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:82
bool internalDirNext(V &view, uint256 const &root, std::shared_ptr< N > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:34
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition Indexes.cpp:178
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:324
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:196
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:356
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:508
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:428
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:172
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:226
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:522
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:362
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1636
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3569
@ telFAILED_PROCESSING
Definition TER.h:37
@ terNO_AMM
Definition TER.h:208
@ terNO_RIPPLE
Definition TER.h:205
@ terNO_ACCOUNT
Definition TER.h:198
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1153
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:676
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1440
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:59
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
bool dirFirst(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:105
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1129
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:963
SpendableHandling
Controls whether to include the account's full spendable balance.
Definition View.h:65
@ shFULL_BALANCE
Definition View.h:65
bool isXRP(AccountID const &c)
Definition AccountID.h:71
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1275
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:253
std::map< uint256, NetClock::time_point > majorityAmendments_t
Definition View.h:473
std::set< uint256 > getEnabledAmendments(ReadView const &view)
Definition View.cpp:977
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2877
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:205
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
TER removeEmptyHolding(ApplyView &view, AccountID const &accountID, Issue const &issue, beast::Journal journal)
Definition View.cpp:1763
bool isVaultPseudoAccountFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptShare, int depth)
Definition View.cpp:289
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
TER rippleUnlockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, AccountID const &uGranteeID, STAmount const &netAmount, STAmount const &grossAmount, beast::Journal j)
Definition View.cpp:3749
TER doWithdraw(ApplyView &view, STTx const &tx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:1391
static TER accountSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2376
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:155
static TER rippleSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2576
@ expired
List is expired, but has the largest non-pending sequence seen so far.
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:235
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:713
@ tefBAD_LEDGER
Definition TER.h:151
@ tefINTERNAL
Definition TER.h:154
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, int depth=0)
Definition View.cpp:264
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:461
WaiveTransferFee
Definition View.h:25
Number root(Number f, unsigned d)
Definition Number.cpp:997
static TER accountSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2738
static TER rippleCreditMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:2514
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition View.cpp:138
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
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)
Definition View.cpp:1516
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition View.cpp:3464
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
Definition Credit.cpp:9
static TER rippleCreditIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Definition View.cpp:1965
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:332
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition View.cpp:127
static TER rippleSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, STAmount &saActual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2119
static TER rippleSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2642
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
@ current
This was a new validation and was added.
bool areCompatible(ReadView const &validLedger, ReadView const &testLedger, beast::Journal::Stream &s, char const *reason)
Return false if the test ledger is provably incompatible with the valid ledger, that is,...
Definition View.cpp:855
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Definition View.cpp:1179
TruncateShares
Definition View.h:1151
static std::uint32_t confineOwnerCount(std::uint32_t current, std::int32_t adjustment, std::optional< AccountID > const &id=std::nullopt, beast::Journal j=beast::Journal{beast::Journal::getNullSink()})
Definition View.cpp:638
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2778
STLedgerEntry SLE
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3541
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:612
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:164
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1017
std::optional< STAmount > assetsToSharesWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::no)
Definition View.cpp:3598
static TER rippleSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, STAmount &actual, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2174
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:510
TERSubset< CanCvtToTER > TER
Definition TER.h:630
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1088
static STAmount getTrustLineBalance(ReadView const &view, SLE::const_ref sle, AccountID const &account, Currency const &currency, AccountID const &issuer, bool includeOppositeLimit, beast::Journal j)
Definition View.cpp:423
static TER withdrawToDestExceedsLimit(ReadView const &view, AccountID const &from, AccountID const &to, STAmount const &amount)
Definition View.cpp:1315
AuthHandling
Controls the treatment of unauthorized MPT balances.
Definition View.h:62
@ ahIGNORE_AUTH
Definition View.h:62
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:62
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:818
static SLE::const_pointer getLineIfUsable(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:369
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition View.cpp:994
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:3923
AuthType
Definition View.h:967
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
Definition Credit.cpp:46
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3097
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
Definition View.cpp:3326
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:229
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3052
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1106
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:1114
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:195
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1904
AccountID const & noAccount()
A placeholder for empty accounts.
static bool updateTrustLine(ApplyView &view, SLE::pointer state, bool bSenderHigh, AccountID const &sender, STAmount const &before, STAmount const &after, beast::Journal j)
Definition View.cpp:2824
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2977
static TER accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2252
bool canSubtract(STAmount const &amt1, STAmount const &amt2)
Determines if it is safe to subtract one STAmount from another.
Definition STAmount.cpp:590
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
std::optional< STAmount > sharesToAssetsWithdraw(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3627
TER rippleLockEscrowMPT(ApplyView &view, AccountID const &uGrantorID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:3652
LedgerEntryType
Identifiers for on-ledger objects.
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecDIR_FULL
Definition TER.h:269
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:274
@ tecNO_TARGET
Definition TER.h:286
@ tecPATH_DRY
Definition TER.h:276
@ tecINCOMPLETE
Definition TER.h:317
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecNO_AUTH
Definition TER.h:282
@ tecINTERNAL
Definition TER.h:292
@ tecFAILED_PROCESSING
Definition TER.h:268
@ tecFROZEN
Definition TER.h:285
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecEXPIRED
Definition TER.h:296
@ tecNO_LINE
Definition TER.h:283
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecDUPLICATE
Definition TER.h:297
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecNO_DST
Definition TER.h:272
bool forEachItemAfter(ReadView const &view, Keylet const &root, uint256 const &after, std::uint64_t const hint, unsigned int limit, std::function< bool(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items after an item in the given directory.
Definition View.cpp:740
static TER accountSendMultiMPT(ApplyView &view, AccountID const &senderID, MPTIssue const &mptIssue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2763
@ lsfHighReserve
@ lsfDepositAuth
@ lsfLowReserve
@ lsfRequireAuth
@ lsfDefaultRipple
@ lsfLowDeepFreeze
@ lsfMPTRequireAuth
@ lsfRequireDestTag
@ lsfMPTLocked
@ lsfMPTAuthorized
@ lsfMPTCanTransfer
@ lsfLowNoRipple
@ lsfGlobalFreeze
@ lsfLowFreeze
@ lsfDisableMaster
@ lsfHighFreeze
@ lsfHighNoRipple
@ lsfHighDeepFreeze
@ lsfHighAuth
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3225
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::const_ref toSle, STAmount const &amount, bool hasDestinationTag)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:1344
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1199
TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
Validates that the destination SLE and tag are valid.
Definition View.cpp:1285
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
Definition View.cpp:2799
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1864
bool isLPTokenFrozen(ReadView const &view, AccountID const &account, Issue const &asset, Issue const &asset2)
Definition View.cpp:358
@ tesSUCCESS
Definition TER.h:226
Rate const parityRate
A transfer rate signifying a 1:1 exchange.
bool dirNext(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition View.cpp:116
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< std::uint16_t > maxNodesToDelete=std::nullopt)
Cleanup owner directory entries on account delete.
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3514
T size(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
Represents a transfer rate.
Definition Rate.h:21
A field with a type known at compile time.
Definition SField.h:304
Returns the RIPEMD-160 digest of the SHA256 hash of the message.
Definition digest.h:117
T time_since_epoch(T... args)
T visit(T... args)