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