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.header().seq < testLedger.header().seq)
911 {
912 // valid -> ... -> test
913 auto hash = hashOfSeq(
914 testLedger,
915 validLedger.header().seq,
916 beast::Journal{beast::Journal::getNullSink()});
917 if (hash && (*hash != validLedger.header().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.header().seq > testLedger.header().seq)
927 {
928 // test -> ... -> valid
929 auto hash = hashOfSeq(
930 validLedger,
931 testLedger.header().seq,
932 beast::Journal{beast::Journal::getNullSink()});
933 if (hash && (*hash != testLedger.header().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.header().seq == testLedger.header().seq) &&
944 (validLedger.header().hash != testLedger.header().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.header().seq << " "
955 << to_string(validLedger.header().hash);
956
957 JLOG(s) << "New: " << testLedger.header().seq << " "
958 << to_string(testLedger.header().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.header().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.header().seq) &&
991 (testLedger.header().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.header().seq << " "
1003 << to_string(testLedger.header().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.header().hash;
1075 if (seq == (ledger.seq() - 1))
1076 return ledger.header().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.header().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 =
1184 sha512Half(i, view.header().parentHash, pseudoOwnerKey);
1185 rsh(hash.data(), hash.size());
1186 AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
1187 if (!view.read(keylet::account(ret)))
1188 return ret;
1189 }
1190 return beast::zero;
1191}
1192
1193// Pseudo-account designator fields MUST be maintained by including the
1194// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
1195// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
1196// since a non-active amendment will not set any field, by definition.
1197// Specific properties of a pseudo-account are NOT checked here, that's what
1198// InvariantCheck is for.
1199[[nodiscard]] std::vector<SField const*> const&
1201{
1202 static std::vector<SField const*> const pseudoFields = []() {
1203 auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT);
1204 if (!ar)
1205 {
1206 // LCOV_EXCL_START
1207 LogicError(
1208 "ripple::getPseudoAccountFields : unable to find account root "
1209 "ledger "
1210 "format");
1211 // LCOV_EXCL_STOP
1212 }
1213 auto const& soTemplate = ar->getSOTemplate();
1214
1215 std::vector<SField const*> pseudoFields;
1216 for (auto const& field : soTemplate)
1217 {
1218 if (field.sField().shouldMeta(SField::sMD_PseudoAccount))
1219 pseudoFields.emplace_back(&field.sField());
1220 }
1221 return pseudoFields;
1222 }();
1223 return pseudoFields;
1224}
1225
1226[[nodiscard]] bool
1229 std::set<SField const*> const& pseudoFieldFilter)
1230{
1231 auto const& fields = getPseudoAccountFields();
1232
1233 // Intentionally use defensive coding here because it's cheap and makes the
1234 // semantics of true return value clean.
1235 return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
1237 fields.begin(),
1238 fields.end(),
1239 [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
1240 return sleAcct->isFieldPresent(*sf) &&
1241 (pseudoFieldFilter.empty() ||
1242 pseudoFieldFilter.contains(sf));
1243 }) > 0;
1244}
1245
1246Expected<std::shared_ptr<SLE>, TER>
1248 ApplyView& view,
1249 uint256 const& pseudoOwnerKey,
1250 SField const& ownerField)
1251{
1252 [[maybe_unused]]
1253 auto const& fields = getPseudoAccountFields();
1254 XRPL_ASSERT(
1256 fields.begin(),
1257 fields.end(),
1258 [&ownerField](SField const* sf) -> bool {
1259 return *sf == ownerField;
1260 }) == 1,
1261 "ripple::createPseudoAccount : valid owner field");
1262
1263 auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey);
1264 if (accountId == beast::zero)
1265 return Unexpected(tecDUPLICATE);
1266
1267 // Create pseudo-account.
1268 auto account = std::make_shared<SLE>(keylet::account(accountId));
1269 account->setAccountID(sfAccount, accountId);
1270 account->setFieldAmount(sfBalance, STAmount{});
1271
1272 // Pseudo-accounts can't submit transactions, so set the sequence number
1273 // to 0 to make them easier to spot and verify, and add an extra level
1274 // of protection.
1275 std::uint32_t const seqno = //
1276 view.rules().enabled(featureSingleAssetVault) || //
1277 view.rules().enabled(featureLendingProtocol) //
1278 ? 0 //
1279 : view.seq();
1280 account->setFieldU32(sfSequence, seqno);
1281 // Ignore reserves requirement, disable the master key, allow default
1282 // rippling, and enable deposit authorization to prevent payments into
1283 // pseudo-account.
1284 account->setFieldU32(
1286 // Link the pseudo-account with its owner object.
1287 account->setFieldH256(ownerField, pseudoOwnerKey);
1288
1289 view.insert(account);
1290
1291 return account;
1292}
1293
1294[[nodiscard]] TER
1295canAddHolding(ReadView const& view, Issue const& issue)
1296{
1297 if (issue.native())
1298 return tesSUCCESS; // No special checks for XRP
1299
1300 auto const issuer = view.read(keylet::account(issue.getIssuer()));
1301 if (!issuer)
1302 return terNO_ACCOUNT;
1303 else if (!issuer->isFlag(lsfDefaultRipple))
1304 return terNO_RIPPLE;
1305
1306 return tesSUCCESS;
1307}
1308
1309[[nodiscard]] TER
1310canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
1311{
1312 auto mptID = mptIssue.getMptID();
1313 auto issuance = view.read(keylet::mptIssuance(mptID));
1314 if (!issuance)
1315 return tecOBJECT_NOT_FOUND;
1316 if (!issuance->isFlag(lsfMPTCanTransfer))
1317 return tecNO_AUTH;
1318
1319 return tesSUCCESS;
1320}
1321
1322[[nodiscard]] TER
1323canAddHolding(ReadView const& view, Asset const& asset)
1324{
1325 return std::visit(
1326 [&]<ValidIssueType TIss>(TIss const& issue) -> TER {
1327 return canAddHolding(view, issue);
1328 },
1329 asset.value());
1330}
1331
1332[[nodiscard]] TER
1333checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
1334{
1335 if (toSle == nullptr)
1336 return tecNO_DST;
1337
1338 // The tag is basically account-specific information we don't
1339 // understand, but we can require someone to fill it in.
1340 if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
1341 return tecDST_TAG_NEEDED; // Cannot send without a tag
1342
1343 return tesSUCCESS;
1344}
1345
1346[[nodiscard]] TER
1348 AccountID const& from,
1349 ReadView const& view,
1350 AccountID const& to,
1351 SLE::const_ref toSle,
1352 bool hasDestinationTag)
1353{
1354 if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
1355 return ret;
1356
1357 if (from == to)
1358 return tesSUCCESS;
1359
1360 if (toSle->isFlag(lsfDepositAuth))
1361 {
1362 if (!view.exists(keylet::depositPreauth(to, from)))
1363 return tecNO_PERMISSION;
1364 }
1365
1366 return tesSUCCESS;
1367}
1368
1369[[nodiscard]] TER
1371 AccountID const& from,
1372 ReadView const& view,
1373 AccountID const& to,
1374 bool hasDestinationTag)
1375{
1376 auto const toSle = view.read(keylet::account(to));
1377
1378 return canWithdraw(from, view, to, toSle, hasDestinationTag);
1379}
1380
1381[[nodiscard]] TER
1382canWithdraw(ReadView const& view, STTx const& tx)
1383{
1384 auto const from = tx[sfAccount];
1385 auto const to = tx[~sfDestination].value_or(from);
1386
1387 return canWithdraw(from, view, to, tx.isFieldPresent(sfDestinationTag));
1388}
1389
1390TER
1392 ApplyView& view,
1393 STTx const& tx,
1394 AccountID const& senderAcct,
1395 AccountID const& dstAcct,
1396 AccountID const& sourceAcct,
1397 XRPAmount priorBalance,
1398 STAmount const& amount,
1400{
1401 // Create trust line or MPToken for the receiving account
1402 if (dstAcct == senderAcct)
1403 {
1404 if (auto const ter = addEmptyHolding(
1405 view, senderAcct, priorBalance, amount.asset(), j);
1406 !isTesSuccess(ter) && ter != tecDUPLICATE)
1407 return ter;
1408 }
1409 else
1410 {
1411 auto dstSle = view.peek(keylet::account(dstAcct));
1412 if (auto err =
1413 verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
1414 return err;
1415 }
1416
1417 // Sanity check
1418 if (accountHolds(
1419 view,
1420 sourceAcct,
1421 amount.asset(),
1424 j) < amount)
1425 {
1426 // LCOV_EXCL_START
1427 JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "
1428 "broker cover assets.";
1429 return tefINTERNAL;
1430 // LCOV_EXCL_STOP
1431 }
1432
1433 // Move the funds directly from the broker's pseudo-account to the
1434 // dstAcct
1435 return accountSend(
1436 view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
1437}
1438
1439[[nodiscard]] TER
1441 ApplyView& view,
1442 AccountID const& accountID,
1443 XRPAmount priorBalance,
1444 Issue const& issue,
1445 beast::Journal journal)
1446{
1447 // Every account can hold XRP. An issuer can issue directly.
1448 if (issue.native() || accountID == issue.getIssuer())
1449 return tesSUCCESS;
1450
1451 auto const& issuerId = issue.getIssuer();
1452 auto const& currency = issue.currency;
1453 if (isGlobalFrozen(view, issuerId))
1454 return tecFROZEN; // LCOV_EXCL_LINE
1455
1456 auto const& srcId = issuerId;
1457 auto const& dstId = accountID;
1458 auto const high = srcId > dstId;
1459 auto const index = keylet::line(srcId, dstId, currency);
1460 auto const sleSrc = view.peek(keylet::account(srcId));
1461 auto const sleDst = view.peek(keylet::account(dstId));
1462 if (!sleDst || !sleSrc)
1463 return tefINTERNAL; // LCOV_EXCL_LINE
1464 if (!sleSrc->isFlag(lsfDefaultRipple))
1465 return tecINTERNAL; // LCOV_EXCL_LINE
1466 // If the line already exists, don't create it again.
1467 if (view.read(index))
1468 return tecDUPLICATE;
1469
1470 // Can the account cover the trust line reserve ?
1471 std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
1472 if (priorBalance < view.fees().accountReserve(ownerCount + 1))
1474
1475 return trustCreate(
1476 view,
1477 high,
1478 srcId,
1479 dstId,
1480 index.key,
1481 sleDst,
1482 /*auth=*/false,
1483 /*noRipple=*/true,
1484 /*freeze=*/false,
1485 /*deepFreeze*/ false,
1486 /*balance=*/STAmount{Issue{currency, noAccount()}},
1487 /*limit=*/STAmount{Issue{currency, dstId}},
1488 /*qualityIn=*/0,
1489 /*qualityOut=*/0,
1490 journal);
1491}
1492
1493[[nodiscard]] TER
1495 ApplyView& view,
1496 AccountID const& accountID,
1497 XRPAmount priorBalance,
1498 MPTIssue const& mptIssue,
1499 beast::Journal journal)
1500{
1501 auto const& mptID = mptIssue.getMptID();
1502 auto const mpt = view.peek(keylet::mptIssuance(mptID));
1503 if (!mpt)
1504 return tefINTERNAL; // LCOV_EXCL_LINE
1505 if (mpt->isFlag(lsfMPTLocked))
1506 return tefINTERNAL; // LCOV_EXCL_LINE
1507 if (view.peek(keylet::mptoken(mptID, accountID)))
1508 return tecDUPLICATE;
1509 if (accountID == mptIssue.getIssuer())
1510 return tesSUCCESS;
1511
1512 return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
1513}
1514
1515[[nodiscard]] TER
1517 ApplyView& view,
1518 XRPAmount const& priorBalance,
1519 MPTID const& mptIssuanceID,
1520 AccountID const& account,
1521 beast::Journal journal,
1522 std::uint32_t flags,
1523 std::optional<AccountID> holderID)
1524{
1525 auto const sleAcct = view.peek(keylet::account(account));
1526 if (!sleAcct)
1527 return tecINTERNAL; // LCOV_EXCL_LINE
1528
1529 // If the account that submitted the tx is a holder
1530 // Note: `account_` is holder's account
1531 // `holderID` is NOT used
1532 if (!holderID)
1533 {
1534 // When a holder wants to unauthorize/delete a MPT, the ledger must
1535 // - delete mptokenKey from owner directory
1536 // - delete the MPToken
1537 if (flags & tfMPTUnauthorize)
1538 {
1539 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1540 auto const sleMpt = view.peek(mptokenKey);
1541 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
1542 return tecINTERNAL; // LCOV_EXCL_LINE
1543
1544 if (!view.dirRemove(
1545 keylet::ownerDir(account),
1546 (*sleMpt)[sfOwnerNode],
1547 sleMpt->key(),
1548 false))
1549 return tecINTERNAL; // LCOV_EXCL_LINE
1550
1551 adjustOwnerCount(view, sleAcct, -1, journal);
1552
1553 view.erase(sleMpt);
1554 return tesSUCCESS;
1555 }
1556
1557 // A potential holder wants to authorize/hold a mpt, the ledger must:
1558 // - add the new mptokenKey to the owner directory
1559 // - create the MPToken object for the holder
1560
1561 // The reserve that is required to create the MPToken. Note
1562 // that although the reserve increases with every item
1563 // an account owns, in the case of MPTokens we only
1564 // *enforce* a reserve if the user owns more than two
1565 // items. This is similar to the reserve requirements of trust lines.
1566 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
1567 XRPAmount const reserveCreate(
1568 (uOwnerCount < 2) ? XRPAmount(beast::zero)
1569 : view.fees().accountReserve(uOwnerCount + 1));
1570
1571 if (priorBalance < reserveCreate)
1573
1574 // Defensive check before we attempt to create MPToken for the issuer
1575 auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
1576 if (!mpt || mpt->getAccountID(sfIssuer) == account)
1577 {
1578 // LCOV_EXCL_START
1579 UNREACHABLE(
1580 "ripple::authorizeMPToken : invalid issuance or issuers token");
1581 if (view.rules().enabled(featureLendingProtocol))
1582 return tecINTERNAL;
1583 // LCOV_EXCL_STOP
1584 }
1585
1586 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
1587 auto mptoken = std::make_shared<SLE>(mptokenKey);
1588 if (auto ter = dirLink(view, account, mptoken))
1589 return ter; // LCOV_EXCL_LINE
1590
1591 (*mptoken)[sfAccount] = account;
1592 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
1593 (*mptoken)[sfFlags] = 0;
1594 view.insert(mptoken);
1595
1596 // Update owner count.
1597 adjustOwnerCount(view, sleAcct, 1, journal);
1598
1599 return tesSUCCESS;
1600 }
1601
1602 auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
1603 if (!sleMptIssuance)
1604 return tecINTERNAL; // LCOV_EXCL_LINE
1605
1606 // If the account that submitted this tx is the issuer of the MPT
1607 // Note: `account_` is issuer's account
1608 // `holderID` is holder's account
1609 if (account != (*sleMptIssuance)[sfIssuer])
1610 return tecINTERNAL; // LCOV_EXCL_LINE
1611
1612 auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
1613 if (!sleMpt)
1614 return tecINTERNAL; // LCOV_EXCL_LINE
1615
1616 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
1617 std::uint32_t flagsOut = flagsIn;
1618
1619 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
1620 // their MPToken
1621 if (flags & tfMPTUnauthorize)
1622 flagsOut &= ~lsfMPTAuthorized;
1623 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
1624 // MPToken
1625 else
1626 flagsOut |= lsfMPTAuthorized;
1627
1628 if (flagsIn != flagsOut)
1629 sleMpt->setFieldU32(sfFlags, flagsOut);
1630
1631 view.update(sleMpt);
1632 return tesSUCCESS;
1633}
1634
1635TER
1637 ApplyView& view,
1638 bool const bSrcHigh,
1639 AccountID const& uSrcAccountID,
1640 AccountID const& uDstAccountID,
1641 uint256 const& uIndex, // --> ripple state entry
1642 SLE::ref sleAccount, // --> the account being set.
1643 bool const bAuth, // --> authorize account.
1644 bool const bNoRipple, // --> others cannot ripple through
1645 bool const bFreeze, // --> funds cannot leave
1646 bool bDeepFreeze, // --> can neither receive nor send funds
1647 STAmount const& saBalance, // --> balance of account being set.
1648 // Issuer should be noAccount()
1649 STAmount const& saLimit, // --> limit for account being set.
1650 // Issuer should be the account being set.
1651 std::uint32_t uQualityIn,
1652 std::uint32_t uQualityOut,
1654{
1655 JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
1656 << to_string(uDstAccountID) << ", "
1657 << saBalance.getFullText();
1658
1659 auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
1660 auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
1661 if (uLowAccountID == uHighAccountID)
1662 {
1663 // LCOV_EXCL_START
1664 UNREACHABLE("ripple::trustCreate : trust line to self");
1665 if (view.rules().enabled(featureLendingProtocol))
1666 return tecINTERNAL;
1667 // LCOV_EXCL_STOP
1668 }
1669
1670 auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
1671 view.insert(sleRippleState);
1672
1673 auto lowNode = view.dirInsert(
1674 keylet::ownerDir(uLowAccountID),
1675 sleRippleState->key(),
1676 describeOwnerDir(uLowAccountID));
1677
1678 if (!lowNode)
1679 return tecDIR_FULL; // LCOV_EXCL_LINE
1680
1681 auto highNode = view.dirInsert(
1682 keylet::ownerDir(uHighAccountID),
1683 sleRippleState->key(),
1684 describeOwnerDir(uHighAccountID));
1685
1686 if (!highNode)
1687 return tecDIR_FULL; // LCOV_EXCL_LINE
1688
1689 bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
1690 bool const bSetHigh = bSrcHigh ^ bSetDst;
1691
1692 XRPL_ASSERT(sleAccount, "ripple::trustCreate : non-null SLE");
1693 if (!sleAccount)
1694 return tefINTERNAL; // LCOV_EXCL_LINE
1695
1696 XRPL_ASSERT(
1697 sleAccount->getAccountID(sfAccount) ==
1698 (bSetHigh ? uHighAccountID : uLowAccountID),
1699 "ripple::trustCreate : matching account ID");
1700 auto const slePeer =
1701 view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
1702 if (!slePeer)
1703 return tecNO_TARGET;
1704
1705 // Remember deletion hints.
1706 sleRippleState->setFieldU64(sfLowNode, *lowNode);
1707 sleRippleState->setFieldU64(sfHighNode, *highNode);
1708
1709 sleRippleState->setFieldAmount(
1710 bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
1711 sleRippleState->setFieldAmount(
1712 bSetHigh ? sfLowLimit : sfHighLimit,
1714 saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
1715
1716 if (uQualityIn)
1717 sleRippleState->setFieldU32(
1718 bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
1719
1720 if (uQualityOut)
1721 sleRippleState->setFieldU32(
1722 bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
1723
1724 std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
1725
1726 if (bAuth)
1727 {
1728 uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
1729 }
1730 if (bNoRipple)
1731 {
1732 uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
1733 }
1734 if (bFreeze)
1735 {
1736 uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
1737 }
1738 if (bDeepFreeze)
1739 {
1740 uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
1741 }
1742
1743 if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
1744 {
1745 // The other side's default is no rippling
1746 uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
1747 }
1748
1749 sleRippleState->setFieldU32(sfFlags, uFlags);
1750 adjustOwnerCount(view, sleAccount, 1, j);
1751
1752 // ONLY: Create ripple balance.
1753 sleRippleState->setFieldAmount(
1754 sfBalance, bSetHigh ? -saBalance : saBalance);
1755
1756 view.creditHook(
1757 uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
1758
1759 return tesSUCCESS;
1760}
1761
1762[[nodiscard]] TER
1764 ApplyView& view,
1765 AccountID const& accountID,
1766 Issue const& issue,
1767 beast::Journal journal)
1768{
1769 if (issue.native())
1770 {
1771 auto const sle = view.read(keylet::account(accountID));
1772 if (!sle)
1773 return tecINTERNAL; // LCOV_EXCL_LINE
1774
1775 auto const balance = sle->getFieldAmount(sfBalance);
1776 if (balance.xrp() != 0)
1777 return tecHAS_OBLIGATIONS;
1778
1779 return tesSUCCESS;
1780 }
1781
1782 // `asset` is an IOU.
1783 // If the account is the issuer, then no line should exist. Check anyway. If
1784 // a line does exist, it will get deleted. If not, return success.
1785 bool const accountIsIssuer = accountID == issue.account;
1786 auto const line = view.peek(keylet::line(accountID, issue));
1787 if (!line)
1788 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
1789 if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
1790 return tecHAS_OBLIGATIONS;
1791
1792 // Adjust the owner count(s)
1793 if (line->isFlag(lsfLowReserve))
1794 {
1795 // Clear reserve for low account.
1796 auto sleLowAccount =
1797 view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
1798 if (!sleLowAccount)
1799 return tecINTERNAL; // LCOV_EXCL_LINE
1800
1801 adjustOwnerCount(view, sleLowAccount, -1, journal);
1802 // It's not really necessary to clear the reserve flag, since the line
1803 // is about to be deleted, but this will make the metadata reflect an
1804 // accurate state at the time of deletion.
1805 line->clearFlag(lsfLowReserve);
1806 }
1807
1808 if (line->isFlag(lsfHighReserve))
1809 {
1810 // Clear reserve for high account.
1811 auto sleHighAccount =
1812 view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
1813 if (!sleHighAccount)
1814 return tecINTERNAL; // LCOV_EXCL_LINE
1815
1816 adjustOwnerCount(view, sleHighAccount, -1, journal);
1817 // It's not really necessary to clear the reserve flag, since the line
1818 // is about to be deleted, but this will make the metadata reflect an
1819 // accurate state at the time of deletion.
1820 line->clearFlag(lsfHighReserve);
1821 }
1822
1823 return trustDelete(
1824 view,
1825 line,
1826 line->at(sfLowLimit)->getIssuer(),
1827 line->at(sfHighLimit)->getIssuer(),
1828 journal);
1829}
1830
1831[[nodiscard]] TER
1833 ApplyView& view,
1834 AccountID const& accountID,
1835 MPTIssue const& mptIssue,
1836 beast::Journal journal)
1837{
1838 // If the account is the issuer, then no token should exist. MPTs do not
1839 // have the legacy ability to create such a situation, but check anyway. If
1840 // a token does exist, it will get deleted. If not, return success.
1841 bool const accountIsIssuer = accountID == mptIssue.getIssuer();
1842 auto const& mptID = mptIssue.getMptID();
1843 auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
1844 if (!mptoken)
1845 return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
1846 // Unlike a trust line, if the account is the issuer, and the token has a
1847 // balance, it can not just be deleted, because that will throw the issuance
1848 // accounting out of balance, so fail. Since this should be impossible
1849 // anyway, I'm not going to put any effort into it.
1850 if (mptoken->at(sfMPTAmount) != 0)
1851 return tecHAS_OBLIGATIONS;
1852
1853 return authorizeMPToken(
1854 view,
1855 {}, // priorBalance
1856 mptID,
1857 accountID,
1858 journal,
1859 tfMPTUnauthorize // flags
1860 );
1861}
1862
1863TER
1865 ApplyView& view,
1866 std::shared_ptr<SLE> const& sleRippleState,
1867 AccountID const& uLowAccountID,
1868 AccountID const& uHighAccountID,
1870{
1871 // Detect legacy dirs.
1872 std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
1873 std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
1874
1875 JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
1876
1877 if (!view.dirRemove(
1878 keylet::ownerDir(uLowAccountID),
1879 uLowNode,
1880 sleRippleState->key(),
1881 false))
1882 {
1883 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1884 }
1885
1886 JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
1887
1888 if (!view.dirRemove(
1889 keylet::ownerDir(uHighAccountID),
1890 uHighNode,
1891 sleRippleState->key(),
1892 false))
1893 {
1894 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1895 }
1896
1897 JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
1898 view.erase(sleRippleState);
1899
1900 return tesSUCCESS;
1901}
1902
1903TER
1905{
1906 if (!sle)
1907 return tesSUCCESS;
1908 auto offerIndex = sle->key();
1909 auto owner = sle->getAccountID(sfAccount);
1910
1911 // Detect legacy directories.
1912 uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
1913
1914 if (!view.dirRemove(
1915 keylet::ownerDir(owner),
1916 sle->getFieldU64(sfOwnerNode),
1917 offerIndex,
1918 false))
1919 {
1920 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1921 }
1922
1923 if (!view.dirRemove(
1924 keylet::page(uDirectory),
1925 sle->getFieldU64(sfBookNode),
1926 offerIndex,
1927 false))
1928 {
1929 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1930 }
1931
1932 if (sle->isFieldPresent(sfAdditionalBooks))
1933 {
1934 XRPL_ASSERT(
1935 sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
1936 "ripple::offerDelete : should be a hybrid domain offer");
1937
1938 auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
1939
1940 for (auto const& bookDir : additionalBookDirs)
1941 {
1942 auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
1943 auto const& dirNode = bookDir.getFieldU64(sfBookNode);
1944
1945 if (!view.dirRemove(
1946 keylet::page(dirIndex), dirNode, offerIndex, false))
1947 {
1948 return tefBAD_LEDGER; // LCOV_EXCL_LINE
1949 }
1950 }
1951 }
1952
1953 adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
1954
1955 view.erase(sle);
1956
1957 return tesSUCCESS;
1958}
1959
1960// Direct send w/o fees:
1961// - Redeeming IOUs and/or sending sender's own IOUs.
1962// - Create trust line if needed.
1963// --> bCheckIssuer : normally require issuer to be involved.
1964static TER
1966 ApplyView& view,
1967 AccountID const& uSenderID,
1968 AccountID const& uReceiverID,
1969 STAmount const& saAmount,
1970 bool bCheckIssuer,
1972{
1973 AccountID const& issuer = saAmount.getIssuer();
1974 Currency const& currency = saAmount.getCurrency();
1975
1976 // Make sure issuer is involved.
1977 XRPL_ASSERT(
1978 !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer,
1979 "ripple::rippleCreditIOU : matching issuer or don't care");
1980 (void)issuer;
1981
1982 // Disallow sending to self.
1983 XRPL_ASSERT(
1984 uSenderID != uReceiverID,
1985 "ripple::rippleCreditIOU : sender is not receiver");
1986
1987 bool const bSenderHigh = uSenderID > uReceiverID;
1988 auto const index = keylet::line(uSenderID, uReceiverID, currency);
1989
1990 XRPL_ASSERT(
1991 !isXRP(uSenderID) && uSenderID != noAccount(),
1992 "ripple::rippleCreditIOU : sender is not XRP");
1993 XRPL_ASSERT(
1994 !isXRP(uReceiverID) && uReceiverID != noAccount(),
1995 "ripple::rippleCreditIOU : receiver is not XRP");
1996
1997 // If the line exists, modify it accordingly.
1998 if (auto const sleRippleState = view.peek(index))
1999 {
2000 STAmount saBalance = sleRippleState->getFieldAmount(sfBalance);
2001
2002 if (bSenderHigh)
2003 saBalance.negate(); // Put balance in sender terms.
2004
2005 view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
2006
2007 STAmount const saBefore = saBalance;
2008
2009 saBalance -= saAmount;
2010
2011 JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> "
2012 << to_string(uReceiverID)
2013 << " : before=" << saBefore.getFullText()
2014 << " amount=" << saAmount.getFullText()
2015 << " after=" << saBalance.getFullText();
2016
2017 std::uint32_t const uFlags(sleRippleState->getFieldU32(sfFlags));
2018 bool bDelete = false;
2019
2020 // FIXME This NEEDS to be cleaned up and simplified. It's impossible
2021 // for anyone to understand.
2022 if (saBefore > beast::zero
2023 // Sender balance was positive.
2024 && saBalance <= beast::zero
2025 // Sender is zero or negative.
2026 && (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
2027 // Sender reserve is set.
2028 &&
2029 static_cast<bool>(
2030 uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
2031 static_cast<bool>(
2032 view.read(keylet::account(uSenderID))->getFlags() &
2034 !(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
2035 !sleRippleState->getFieldAmount(
2036 !bSenderHigh ? sfLowLimit : sfHighLimit)
2037 // Sender trust limit is 0.
2038 && !sleRippleState->getFieldU32(
2039 !bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
2040 // Sender quality in is 0.
2041 && !sleRippleState->getFieldU32(
2042 !bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
2043 // Sender quality out is 0.
2044 {
2045 // Clear the reserve of the sender, possibly delete the line!
2047 view, view.peek(keylet::account(uSenderID)), -1, j);
2048
2049 // Clear reserve flag.
2050 sleRippleState->setFieldU32(
2051 sfFlags,
2052 uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
2053
2054 // Balance is zero, receiver reserve is clear.
2055 bDelete = !saBalance // Balance is zero.
2056 && !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
2057 // Receiver reserve is clear.
2058 }
2059
2060 if (bSenderHigh)
2061 saBalance.negate();
2062
2063 // Want to reflect balance to zero even if we are deleting line.
2064 sleRippleState->setFieldAmount(sfBalance, saBalance);
2065 // ONLY: Adjust ripple balance.
2066
2067 if (bDelete)
2068 {
2069 return trustDelete(
2070 view,
2071 sleRippleState,
2072 bSenderHigh ? uReceiverID : uSenderID,
2073 !bSenderHigh ? uReceiverID : uSenderID,
2074 j);
2075 }
2076
2077 view.update(sleRippleState);
2078 return tesSUCCESS;
2079 }
2080
2081 STAmount const saReceiverLimit(Issue{currency, uReceiverID});
2082 STAmount saBalance{saAmount};
2083
2084 saBalance.setIssuer(noAccount());
2085
2086 JLOG(j.debug()) << "rippleCreditIOU: "
2087 "create line: "
2088 << to_string(uSenderID) << " -> " << to_string(uReceiverID)
2089 << " : " << saAmount.getFullText();
2090
2091 auto const sleAccount = view.peek(keylet::account(uReceiverID));
2092 if (!sleAccount)
2093 return tefINTERNAL; // LCOV_EXCL_LINE
2094
2095 bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
2096
2097 return trustCreate(
2098 view,
2099 bSenderHigh,
2100 uSenderID,
2101 uReceiverID,
2102 index.key,
2103 sleAccount,
2104 false,
2105 noRipple,
2106 false,
2107 false,
2108 saBalance,
2109 saReceiverLimit,
2110 0,
2111 0,
2112 j);
2113}
2114
2115// Send regardless of limits.
2116// --> saAmount: Amount/currency/issuer to deliver to receiver.
2117// <-- saActual: Amount actually cost. Sender pays fees.
2118static TER
2120 ApplyView& view,
2121 AccountID const& uSenderID,
2122 AccountID const& uReceiverID,
2123 STAmount const& saAmount,
2124 STAmount& saActual,
2126 WaiveTransferFee waiveFee)
2127{
2128 auto const& issuer = saAmount.getIssuer();
2129
2130 XRPL_ASSERT(
2131 !isXRP(uSenderID) && !isXRP(uReceiverID),
2132 "ripple::rippleSendIOU : neither sender nor receiver is XRP");
2133 XRPL_ASSERT(
2134 uSenderID != uReceiverID,
2135 "ripple::rippleSendIOU : sender is not receiver");
2136
2137 if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
2138 {
2139 // Direct send: redeeming IOUs and/or sending own IOUs.
2140 auto const ter =
2141 rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
2142 if (ter != tesSUCCESS)
2143 return ter;
2144 saActual = saAmount;
2145 return tesSUCCESS;
2146 }
2147
2148 // Sending 3rd party IOUs: transit.
2149
2150 // Calculate the amount to transfer accounting
2151 // for any transfer fees if the fee is not waived:
2152 saActual = (waiveFee == WaiveTransferFee::Yes)
2153 ? saAmount
2154 : multiply(saAmount, transferRate(view, issuer));
2155
2156 JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
2157 << to_string(uReceiverID)
2158 << " : deliver=" << saAmount.getFullText()
2159 << " cost=" << saActual.getFullText();
2160
2161 TER terResult =
2162 rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j);
2163
2164 if (tesSUCCESS == terResult)
2165 terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j);
2166
2167 return terResult;
2168}
2169
2170// Send regardless of limits.
2171// --> receivers: Amount/currency/issuer to deliver to receivers.
2172// <-- saActual: Amount actually cost to sender. Sender pays fees.
2173static TER
2175 ApplyView& view,
2176 AccountID const& senderID,
2177 Issue const& issue,
2178 MultiplePaymentDestinations const& receivers,
2179 STAmount& actual,
2181 WaiveTransferFee waiveFee)
2182{
2183 auto const& issuer = issue.getIssuer();
2184
2185 XRPL_ASSERT(
2186 !isXRP(senderID), "ripple::rippleSendMultiIOU : sender is not XRP");
2187
2188 // These may diverge
2189 STAmount takeFromSender{issue};
2190 actual = takeFromSender;
2191
2192 // Failures return immediately.
2193 for (auto const& r : receivers)
2194 {
2195 auto const& receiverID = r.first;
2196 STAmount amount{issue, r.second};
2197
2198 /* If we aren't sending anything or if the sender is the same as the
2199 * receiver then we don't need to do anything.
2200 */
2201 if (!amount || (senderID == receiverID))
2202 continue;
2203
2204 XRPL_ASSERT(
2205 !isXRP(receiverID),
2206 "ripple::rippleSendMultiIOU : receiver is not XRP");
2207
2208 if (senderID == issuer || receiverID == issuer || issuer == noAccount())
2209 {
2210 // Direct send: redeeming IOUs and/or sending own IOUs.
2211 if (auto const ter = rippleCreditIOU(
2212 view, senderID, receiverID, amount, false, j))
2213 return ter;
2214 actual += amount;
2215 // Do not add amount to takeFromSender, because rippleCreditIOU took
2216 // it.
2217
2218 continue;
2219 }
2220
2221 // Sending 3rd party IOUs: transit.
2222
2223 // Calculate the amount to transfer accounting
2224 // for any transfer fees if the fee is not waived:
2225 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
2226 ? amount
2227 : multiply(amount, transferRate(view, issuer));
2228 actual += actualSend;
2229 takeFromSender += actualSend;
2230
2231 JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID)
2232 << " - > " << to_string(receiverID)
2233 << " : deliver=" << amount.getFullText()
2234 << " cost=" << actual.getFullText();
2235
2236 if (TER const terResult =
2237 rippleCreditIOU(view, issuer, receiverID, amount, true, j))
2238 return terResult;
2239 }
2240
2241 if (senderID != issuer && takeFromSender)
2242 {
2243 if (TER const terResult = rippleCreditIOU(
2244 view, senderID, issuer, takeFromSender, true, j))
2245 return terResult;
2246 }
2247
2248 return tesSUCCESS;
2249}
2250
2251static TER
2253 ApplyView& view,
2254 AccountID const& uSenderID,
2255 AccountID const& uReceiverID,
2256 STAmount const& saAmount,
2258 WaiveTransferFee waiveFee)
2259{
2260 if (view.rules().enabled(fixAMMv1_1))
2261 {
2262 if (saAmount < beast::zero || saAmount.holds<MPTIssue>())
2263 {
2264 return tecINTERNAL; // LCOV_EXCL_LINE
2265 }
2266 }
2267 else
2268 {
2269 // LCOV_EXCL_START
2270 XRPL_ASSERT(
2271 saAmount >= beast::zero && !saAmount.holds<MPTIssue>(),
2272 "ripple::accountSendIOU : minimum amount and not MPT");
2273 // LCOV_EXCL_STOP
2274 }
2275
2276 /* If we aren't sending anything or if the sender is the same as the
2277 * receiver then we don't need to do anything.
2278 */
2279 if (!saAmount || (uSenderID == uReceiverID))
2280 return tesSUCCESS;
2281
2282 if (!saAmount.native())
2283 {
2284 STAmount saActual;
2285
2286 JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
2287 << to_string(uReceiverID) << " : "
2288 << saAmount.getFullText();
2289
2290 return rippleSendIOU(
2291 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
2292 }
2293
2294 /* XRP send which does not check reserve and can do pure adjustment.
2295 * Note that sender or receiver may be null and this not a mistake; this
2296 * setup is used during pathfinding and it is carefully controlled to
2297 * ensure that transfers are balanced.
2298 */
2299 TER terResult(tesSUCCESS);
2300
2301 SLE::pointer sender = uSenderID != beast::zero
2302 ? view.peek(keylet::account(uSenderID))
2303 : SLE::pointer();
2304 SLE::pointer receiver = uReceiverID != beast::zero
2305 ? view.peek(keylet::account(uReceiverID))
2306 : SLE::pointer();
2307
2308 if (auto stream = j.trace())
2309 {
2310 std::string sender_bal("-");
2311 std::string receiver_bal("-");
2312
2313 if (sender)
2314 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2315
2316 if (receiver)
2317 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
2318
2319 stream << "accountSendIOU> " << to_string(uSenderID) << " ("
2320 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
2321 << receiver_bal << ") : " << saAmount.getFullText();
2322 }
2323
2324 if (sender)
2325 {
2326 if (sender->getFieldAmount(sfBalance) < saAmount)
2327 {
2328 // VFALCO Its laborious to have to mutate the
2329 // TER based on params everywhere
2330 // LCOV_EXCL_START
2331 terResult = view.open() ? TER{telFAILED_PROCESSING}
2333 // LCOV_EXCL_STOP
2334 }
2335 else
2336 {
2337 auto const sndBal = sender->getFieldAmount(sfBalance);
2338 view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
2339
2340 // Decrement XRP balance.
2341 sender->setFieldAmount(sfBalance, sndBal - saAmount);
2342 view.update(sender);
2343 }
2344 }
2345
2346 if (tesSUCCESS == terResult && receiver)
2347 {
2348 // Increment XRP balance.
2349 auto const rcvBal = receiver->getFieldAmount(sfBalance);
2350 receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
2351 view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
2352
2353 view.update(receiver);
2354 }
2355
2356 if (auto stream = j.trace())
2357 {
2358 std::string sender_bal("-");
2359 std::string receiver_bal("-");
2360
2361 if (sender)
2362 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2363
2364 if (receiver)
2365 receiver_bal = receiver->getFieldAmount(sfBalance).getFullText();
2366
2367 stream << "accountSendIOU< " << to_string(uSenderID) << " ("
2368 << sender_bal << ") -> " << to_string(uReceiverID) << " ("
2369 << receiver_bal << ") : " << saAmount.getFullText();
2370 }
2371
2372 return terResult;
2373}
2374
2375static TER
2377 ApplyView& view,
2378 AccountID const& senderID,
2379 Issue const& issue,
2380 MultiplePaymentDestinations const& receivers,
2382 WaiveTransferFee waiveFee)
2383{
2384 XRPL_ASSERT_PARTS(
2385 receivers.size() > 1,
2386 "ripple::accountSendMultiIOU",
2387 "multiple recipients provided");
2388
2389 if (!issue.native())
2390 {
2391 STAmount actual;
2392 JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID)
2393 << " sending " << receivers.size() << " IOUs";
2394
2395 return rippleSendMultiIOU(
2396 view, senderID, issue, receivers, actual, j, waiveFee);
2397 }
2398
2399 /* XRP send which does not check reserve and can do pure adjustment.
2400 * Note that sender or receiver may be null and this not a mistake; this
2401 * setup could be used during pathfinding and it is carefully controlled to
2402 * ensure that transfers are balanced.
2403 */
2404
2405 SLE::pointer sender = senderID != beast::zero
2406 ? view.peek(keylet::account(senderID))
2407 : SLE::pointer();
2408
2409 if (auto stream = j.trace())
2410 {
2411 std::string sender_bal("-");
2412
2413 if (sender)
2414 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2415
2416 stream << "accountSendMultiIOU> " << to_string(senderID) << " ("
2417 << sender_bal << ") -> " << receivers.size() << " receivers.";
2418 }
2419
2420 // Failures return immediately.
2421 STAmount takeFromSender{issue};
2422 for (auto const& r : receivers)
2423 {
2424 auto const& receiverID = r.first;
2425 STAmount amount{issue, r.second};
2426
2427 if (amount < beast::zero)
2428 {
2429 return tecINTERNAL; // LCOV_EXCL_LINE
2430 }
2431
2432 /* If we aren't sending anything or if the sender is the same as the
2433 * receiver then we don't need to do anything.
2434 */
2435 if (!amount || (senderID == receiverID))
2436 continue;
2437
2438 SLE::pointer receiver = receiverID != beast::zero
2439 ? view.peek(keylet::account(receiverID))
2440 : SLE::pointer();
2441
2442 if (auto stream = j.trace())
2443 {
2444 std::string receiver_bal("-");
2445
2446 if (receiver)
2447 receiver_bal =
2448 receiver->getFieldAmount(sfBalance).getFullText();
2449
2450 stream << "accountSendMultiIOU> " << to_string(senderID) << " -> "
2451 << to_string(receiverID) << " (" << receiver_bal
2452 << ") : " << amount.getFullText();
2453 }
2454
2455 if (receiver)
2456 {
2457 // Increment XRP balance.
2458 auto const rcvBal = receiver->getFieldAmount(sfBalance);
2459 receiver->setFieldAmount(sfBalance, rcvBal + amount);
2460 view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
2461
2462 view.update(receiver);
2463
2464 // Take what is actually sent
2465 takeFromSender += amount;
2466 }
2467
2468 if (auto stream = j.trace())
2469 {
2470 std::string receiver_bal("-");
2471
2472 if (receiver)
2473 receiver_bal =
2474 receiver->getFieldAmount(sfBalance).getFullText();
2475
2476 stream << "accountSendMultiIOU< " << to_string(senderID) << " -> "
2477 << to_string(receiverID) << " (" << receiver_bal
2478 << ") : " << amount.getFullText();
2479 }
2480 }
2481
2482 if (sender)
2483 {
2484 if (sender->getFieldAmount(sfBalance) < takeFromSender)
2485 {
2486 return TER{tecFAILED_PROCESSING};
2487 }
2488 else
2489 {
2490 auto const sndBal = sender->getFieldAmount(sfBalance);
2491 view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
2492
2493 // Decrement XRP balance.
2494 sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
2495 view.update(sender);
2496 }
2497 }
2498
2499 if (auto stream = j.trace())
2500 {
2501 std::string sender_bal("-");
2502 std::string receiver_bal("-");
2503
2504 if (sender)
2505 sender_bal = sender->getFieldAmount(sfBalance).getFullText();
2506
2507 stream << "accountSendMultiIOU< " << to_string(senderID) << " ("
2508 << sender_bal << ") -> " << receivers.size() << " receivers.";
2509 }
2510 return tesSUCCESS;
2511}
2512
2513static TER
2515 ApplyView& view,
2516 AccountID const& uSenderID,
2517 AccountID const& uReceiverID,
2518 STAmount const& saAmount,
2520{
2521 // Do not check MPT authorization here - it must have been checked earlier
2522 auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
2523 auto const& issuer = saAmount.getIssuer();
2524 auto sleIssuance = view.peek(mptID);
2525 if (!sleIssuance)
2526 return tecOBJECT_NOT_FOUND;
2527 if (uSenderID == issuer)
2528 {
2529 (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
2530 view.update(sleIssuance);
2531 }
2532 else
2533 {
2534 auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
2535 if (auto sle = view.peek(mptokenID))
2536 {
2537 auto const amt = sle->getFieldU64(sfMPTAmount);
2538 auto const pay = saAmount.mpt().value();
2539 if (amt < pay)
2540 return tecINSUFFICIENT_FUNDS;
2541 (*sle)[sfMPTAmount] = amt - pay;
2542 view.update(sle);
2543 }
2544 else
2545 return tecNO_AUTH;
2546 }
2547
2548 if (uReceiverID == issuer)
2549 {
2550 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
2551 auto const redeem = saAmount.mpt().value();
2552 if (outstanding >= redeem)
2553 {
2554 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
2555 view.update(sleIssuance);
2556 }
2557 else
2558 return tecINTERNAL; // LCOV_EXCL_LINE
2559 }
2560 else
2561 {
2562 auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
2563 if (auto sle = view.peek(mptokenID))
2564 {
2565 (*sle)[sfMPTAmount] += saAmount.mpt().value();
2566 view.update(sle);
2567 }
2568 else
2569 return tecNO_AUTH;
2570 }
2571
2572 return tesSUCCESS;
2573}
2574
2575static TER
2577 ApplyView& view,
2578 AccountID const& uSenderID,
2579 AccountID const& uReceiverID,
2580 STAmount const& saAmount,
2581 STAmount& saActual,
2583 WaiveTransferFee waiveFee)
2584{
2585 XRPL_ASSERT(
2586 uSenderID != uReceiverID,
2587 "ripple::rippleSendMPT : sender is not receiver");
2588
2589 // Safe to get MPT since rippleSendMPT is only called by accountSendMPT
2590 auto const& issuer = saAmount.getIssuer();
2591
2592 auto const sle =
2593 view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
2594 if (!sle)
2595 return tecOBJECT_NOT_FOUND;
2596
2597 if (uSenderID == issuer || uReceiverID == issuer)
2598 {
2599 // if sender is issuer, check that the new OutstandingAmount will not
2600 // exceed MaximumAmount
2601 if (uSenderID == issuer)
2602 {
2603 auto const sendAmount = saAmount.mpt().value();
2604 auto const maximumAmount =
2605 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
2606 if (sendAmount > maximumAmount ||
2607 sle->getFieldU64(sfOutstandingAmount) >
2608 maximumAmount - sendAmount)
2609 return tecPATH_DRY;
2610 }
2611
2612 // Direct send: redeeming MPTs and/or sending own MPTs.
2613 auto const ter =
2614 rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j);
2615 if (ter != tesSUCCESS)
2616 return ter;
2617 saActual = saAmount;
2618 return tesSUCCESS;
2619 }
2620
2621 // Sending 3rd party MPTs: transit.
2622 saActual = (waiveFee == WaiveTransferFee::Yes)
2623 ? saAmount
2624 : multiply(
2625 saAmount,
2626 transferRate(view, saAmount.get<MPTIssue>().getMptID()));
2627
2628 JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
2629 << to_string(uReceiverID)
2630 << " : deliver=" << saAmount.getFullText()
2631 << " cost=" << saActual.getFullText();
2632
2633 if (auto const terResult =
2634 rippleCreditMPT(view, issuer, uReceiverID, saAmount, j);
2635 terResult != tesSUCCESS)
2636 return terResult;
2637
2638 return rippleCreditMPT(view, uSenderID, issuer, saActual, j);
2639}
2640
2641static TER
2643 ApplyView& view,
2644 AccountID const& senderID,
2645 MPTIssue const& mptIssue,
2646 MultiplePaymentDestinations const& receivers,
2647 STAmount& actual,
2649 WaiveTransferFee waiveFee)
2650{
2651 // Safe to get MPT since rippleSendMultiMPT is only called by
2652 // accountSendMultiMPT
2653 auto const& issuer = mptIssue.getIssuer();
2654
2655 auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
2656 if (!sle)
2657 return tecOBJECT_NOT_FOUND;
2658
2659 // These may diverge
2660 STAmount takeFromSender{mptIssue};
2661 actual = takeFromSender;
2662
2663 for (auto const& r : receivers)
2664 {
2665 auto const& receiverID = r.first;
2666 STAmount amount{mptIssue, r.second};
2667
2668 if (amount < beast::zero)
2669 {
2670 return tecINTERNAL; // LCOV_EXCL_LINE
2671 }
2672
2673 /* If we aren't sending anything or if the sender is the same as the
2674 * receiver then we don't need to do anything.
2675 */
2676 if (!amount || (senderID == receiverID))
2677 continue;
2678
2679 if (senderID == issuer || receiverID == issuer)
2680 {
2681 // if sender is issuer, check that the new OutstandingAmount will
2682 // not exceed MaximumAmount
2683 if (senderID == issuer)
2684 {
2685 XRPL_ASSERT_PARTS(
2686 takeFromSender == beast::zero,
2687 "rippler::rippleSendMultiMPT",
2688 "sender == issuer, takeFromSender == zero");
2689 auto const sendAmount = amount.mpt().value();
2690 auto const maximumAmount =
2691 sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
2692 if (sendAmount > maximumAmount ||
2693 sle->getFieldU64(sfOutstandingAmount) >
2694 maximumAmount - sendAmount)
2695 return tecPATH_DRY;
2696 }
2697
2698 // Direct send: redeeming MPTs and/or sending own MPTs.
2699 if (auto const ter =
2700 rippleCreditMPT(view, senderID, receiverID, amount, j))
2701 return ter;
2702 actual += amount;
2703 // Do not add amount to takeFromSender, because rippleCreditMPT took
2704 // it
2705
2706 continue;
2707 }
2708
2709 // Sending 3rd party MPTs: transit.
2710 STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
2711 ? amount
2712 : multiply(
2713 amount,
2714 transferRate(view, amount.get<MPTIssue>().getMptID()));
2715 actual += actualSend;
2716 takeFromSender += actualSend;
2717
2718 JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID)
2719 << " - > " << to_string(receiverID)
2720 << " : deliver=" << amount.getFullText()
2721 << " cost=" << actualSend.getFullText();
2722
2723 if (auto const terResult =
2724 rippleCreditMPT(view, issuer, receiverID, amount, j))
2725 return terResult;
2726 }
2727 if (senderID != issuer && takeFromSender)
2728 {
2729 if (TER const terResult =
2730 rippleCreditMPT(view, senderID, issuer, takeFromSender, j))
2731 return terResult;
2732 }
2733
2734 return tesSUCCESS;
2735}
2736
2737static TER
2739 ApplyView& view,
2740 AccountID const& uSenderID,
2741 AccountID const& uReceiverID,
2742 STAmount const& saAmount,
2744 WaiveTransferFee waiveFee)
2745{
2746 XRPL_ASSERT(
2747 saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
2748 "ripple::accountSendMPT : minimum amount and MPT");
2749
2750 /* If we aren't sending anything or if the sender is the same as the
2751 * receiver then we don't need to do anything.
2752 */
2753 if (!saAmount || (uSenderID == uReceiverID))
2754 return tesSUCCESS;
2755
2756 STAmount saActual{saAmount.asset()};
2757
2758 return rippleSendMPT(
2759 view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
2760}
2761
2762static TER
2764 ApplyView& view,
2765 AccountID const& senderID,
2766 MPTIssue const& mptIssue,
2767 MultiplePaymentDestinations const& receivers,
2769 WaiveTransferFee waiveFee)
2770{
2771 STAmount actual;
2772
2773 return rippleSendMultiMPT(
2774 view, senderID, mptIssue, receivers, actual, j, waiveFee);
2775}
2776
2777TER
2779 ApplyView& view,
2780 AccountID const& uSenderID,
2781 AccountID const& uReceiverID,
2782 STAmount const& saAmount,
2784 WaiveTransferFee waiveFee)
2785{
2786 return std::visit(
2787 [&]<ValidIssueType TIss>(TIss const& issue) {
2788 if constexpr (std::is_same_v<TIss, Issue>)
2789 return accountSendIOU(
2790 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2791 else
2792 return accountSendMPT(
2793 view, uSenderID, uReceiverID, saAmount, j, waiveFee);
2794 },
2795 saAmount.asset().value());
2796}
2797
2798TER
2800 ApplyView& view,
2801 AccountID const& senderID,
2802 Asset const& asset,
2803 MultiplePaymentDestinations const& receivers,
2805 WaiveTransferFee waiveFee)
2806{
2807 XRPL_ASSERT_PARTS(
2808 receivers.size() > 1,
2809 "ripple::accountSendMulti",
2810 "multiple recipients provided");
2811 return std::visit(
2812 [&]<ValidIssueType TIss>(TIss const& issue) {
2813 if constexpr (std::is_same_v<TIss, Issue>)
2814 return accountSendMultiIOU(
2815 view, senderID, issue, receivers, j, waiveFee);
2816 else
2817 return accountSendMultiMPT(
2818 view, senderID, issue, receivers, j, waiveFee);
2819 },
2820 asset.value());
2821}
2822
2823static bool
2825 ApplyView& view,
2826 SLE::pointer state,
2827 bool bSenderHigh,
2828 AccountID const& sender,
2829 STAmount const& before,
2830 STAmount const& after,
2832{
2833 if (!state)
2834 return false;
2835 std::uint32_t const flags(state->getFieldU32(sfFlags));
2836
2837 auto sle = view.peek(keylet::account(sender));
2838 if (!sle)
2839 return false;
2840
2841 // YYY Could skip this if rippling in reverse.
2842 if (before > beast::zero
2843 // Sender balance was positive.
2844 && after <= beast::zero
2845 // Sender is zero or negative.
2846 && (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
2847 // Sender reserve is set.
2848 && static_cast<bool>(
2849 flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
2850 static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
2851 !(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
2852 !state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
2853 // Sender trust limit is 0.
2854 && !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
2855 // Sender quality in is 0.
2856 &&
2857 !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
2858 // Sender quality out is 0.
2859 {
2860 // VFALCO Where is the line being deleted?
2861 // Clear the reserve of the sender, possibly delete the line!
2862 adjustOwnerCount(view, sle, -1, j);
2863
2864 // Clear reserve flag.
2865 state->setFieldU32(
2866 sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
2867
2868 // Balance is zero, receiver reserve is clear.
2869 if (!after // Balance is zero.
2870 && !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
2871 return true;
2872 }
2873 return false;
2874}
2875
2876TER
2878 ApplyView& view,
2879 AccountID const& account,
2880 STAmount const& amount,
2881 Issue const& issue,
2883{
2884 XRPL_ASSERT(
2885 !isXRP(account) && !isXRP(issue.account),
2886 "ripple::issueIOU : neither account nor issuer is XRP");
2887
2888 // Consistency check
2889 XRPL_ASSERT(issue == amount.issue(), "ripple::issueIOU : matching issue");
2890
2891 // Can't send to self!
2892 XRPL_ASSERT(
2893 issue.account != account, "ripple::issueIOU : not issuer account");
2894
2895 JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": "
2896 << amount.getFullText();
2897
2898 bool bSenderHigh = issue.account > account;
2899
2900 auto const index = keylet::line(issue.account, account, issue.currency);
2901
2902 if (auto state = view.peek(index))
2903 {
2904 STAmount final_balance = state->getFieldAmount(sfBalance);
2905
2906 if (bSenderHigh)
2907 final_balance.negate(); // Put balance in sender terms.
2908
2909 STAmount const start_balance = final_balance;
2910
2911 final_balance -= amount;
2912
2913 auto const must_delete = updateTrustLine(
2914 view,
2915 state,
2916 bSenderHigh,
2917 issue.account,
2918 start_balance,
2919 final_balance,
2920 j);
2921
2922 view.creditHook(issue.account, account, amount, start_balance);
2923
2924 if (bSenderHigh)
2925 final_balance.negate();
2926
2927 // Adjust the balance on the trust line if necessary. We do this even if
2928 // we are going to delete the line to reflect the correct balance at the
2929 // time of deletion.
2930 state->setFieldAmount(sfBalance, final_balance);
2931 if (must_delete)
2932 return trustDelete(
2933 view,
2934 state,
2935 bSenderHigh ? account : issue.account,
2936 bSenderHigh ? issue.account : account,
2937 j);
2938
2939 view.update(state);
2940
2941 return tesSUCCESS;
2942 }
2943
2944 // NIKB TODO: The limit uses the receiver's account as the issuer and
2945 // this is unnecessarily inefficient as copying which could be avoided
2946 // is now required. Consider available options.
2947 STAmount const limit(Issue{issue.currency, account});
2948 STAmount final_balance = amount;
2949
2950 final_balance.setIssuer(noAccount());
2951
2952 auto const receiverAccount = view.peek(keylet::account(account));
2953 if (!receiverAccount)
2954 return tefINTERNAL; // LCOV_EXCL_LINE
2955
2956 bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
2957
2958 return trustCreate(
2959 view,
2960 bSenderHigh,
2961 issue.account,
2962 account,
2963 index.key,
2964 receiverAccount,
2965 false,
2966 noRipple,
2967 false,
2968 false,
2969 final_balance,
2970 limit,
2971 0,
2972 0,
2973 j);
2974}
2975
2976TER
2978 ApplyView& view,
2979 AccountID const& account,
2980 STAmount const& amount,
2981 Issue const& issue,
2983{
2984 XRPL_ASSERT(
2985 !isXRP(account) && !isXRP(issue.account),
2986 "ripple::redeemIOU : neither account nor issuer is XRP");
2987
2988 // Consistency check
2989 XRPL_ASSERT(issue == amount.issue(), "ripple::redeemIOU : matching issue");
2990
2991 // Can't send to self!
2992 XRPL_ASSERT(
2993 issue.account != account, "ripple::redeemIOU : not issuer account");
2994
2995 JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": "
2996 << amount.getFullText();
2997
2998 bool bSenderHigh = account > issue.account;
2999
3000 if (auto state =
3001 view.peek(keylet::line(account, issue.account, issue.currency)))
3002 {
3003 STAmount final_balance = state->getFieldAmount(sfBalance);
3004
3005 if (bSenderHigh)
3006 final_balance.negate(); // Put balance in sender terms.
3007
3008 STAmount const start_balance = final_balance;
3009
3010 final_balance -= amount;
3011
3012 auto const must_delete = updateTrustLine(
3013 view, state, bSenderHigh, account, start_balance, final_balance, j);
3014
3015 view.creditHook(account, issue.account, amount, start_balance);
3016
3017 if (bSenderHigh)
3018 final_balance.negate();
3019
3020 // Adjust the balance on the trust line if necessary. We do this even if
3021 // we are going to delete the line to reflect the correct balance at the
3022 // time of deletion.
3023 state->setFieldAmount(sfBalance, final_balance);
3024
3025 if (must_delete)
3026 {
3027 return trustDelete(
3028 view,
3029 state,
3030 bSenderHigh ? issue.account : account,
3031 bSenderHigh ? account : issue.account,
3032 j);
3033 }
3034
3035 view.update(state);
3036 return tesSUCCESS;
3037 }
3038
3039 // In order to hold an IOU, a trust line *MUST* exist to track the
3040 // balance. If it doesn't, then something is very wrong. Don't try
3041 // to continue.
3042 // LCOV_EXCL_START
3043 JLOG(j.fatal()) << "redeemIOU: " << to_string(account)
3044 << " attempts to redeem " << amount.getFullText()
3045 << " but no trust line exists!";
3046
3047 return tefINTERNAL;
3048 // LCOV_EXCL_STOP
3049}
3050
3051TER
3053 ApplyView& view,
3054 AccountID const& from,
3055 AccountID const& to,
3056 STAmount const& amount,
3058{
3059 XRPL_ASSERT(
3060 from != beast::zero, "ripple::transferXRP : nonzero from account");
3061 XRPL_ASSERT(to != beast::zero, "ripple::transferXRP : nonzero to account");
3062 XRPL_ASSERT(from != to, "ripple::transferXRP : sender is not receiver");
3063 XRPL_ASSERT(amount.native(), "ripple::transferXRP : amount is XRP");
3064
3065 SLE::pointer const sender = view.peek(keylet::account(from));
3066 SLE::pointer const receiver = view.peek(keylet::account(to));
3067 if (!sender || !receiver)
3068 return tefINTERNAL; // LCOV_EXCL_LINE
3069
3070 JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> "
3071 << to_string(to) << ") : " << amount.getFullText();
3072
3073 if (sender->getFieldAmount(sfBalance) < amount)
3074 {
3075 // VFALCO Its unfortunate we have to keep
3076 // mutating these TER everywhere
3077 // FIXME: this logic should be moved to callers maybe?
3078 // LCOV_EXCL_START
3079 return view.open() ? TER{telFAILED_PROCESSING}
3081 // LCOV_EXCL_STOP
3082 }
3083
3084 // Decrement XRP balance.
3085 sender->setFieldAmount(
3086 sfBalance, sender->getFieldAmount(sfBalance) - amount);
3087 view.update(sender);
3088
3089 receiver->setFieldAmount(
3090 sfBalance, receiver->getFieldAmount(sfBalance) + amount);
3091 view.update(receiver);
3092
3093 return tesSUCCESS;
3094}
3095
3096TER
3098 ReadView const& view,
3099 Issue const& issue,
3100 AccountID const& account,
3101 AuthType authType)
3102{
3103 if (isXRP(issue) || issue.account == account)
3104 return tesSUCCESS;
3105
3106 auto const trustLine =
3107 view.read(keylet::line(account, issue.account, issue.currency));
3108 // If account has no line, and this is a strong check, fail
3109 if (!trustLine && authType == AuthType::StrongAuth)
3110 return tecNO_LINE;
3111
3112 // If this is a weak or legacy check, or if the account has a line, fail if
3113 // auth is required and not set on the line
3114 if (auto const issuerAccount = view.read(keylet::account(issue.account));
3115 issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
3116 {
3117 if (trustLine)
3118 return ((*trustLine)[sfFlags] &
3119 ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
3120 ? tesSUCCESS
3121 : TER{tecNO_AUTH};
3122 return TER{tecNO_LINE};
3123 }
3124
3125 return tesSUCCESS;
3126}
3127
3128TER
3130 ReadView const& view,
3131 MPTIssue const& mptIssue,
3132 AccountID const& account,
3133 AuthType authType,
3134 int depth)
3135{
3136 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3137 auto const sleIssuance = view.read(mptID);
3138 if (!sleIssuance)
3139 return tecOBJECT_NOT_FOUND;
3140
3141 auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
3142
3143 // issuer is always "authorized"
3144 if (mptIssuer == account) // Issuer won't have MPToken
3145 return tesSUCCESS;
3146
3147 bool const featureSAVEnabled =
3148 view.rules().enabled(featureSingleAssetVault);
3149
3150 if (featureSAVEnabled)
3151 {
3152 if (depth >= maxAssetCheckDepth)
3153 return tecINTERNAL; // LCOV_EXCL_LINE
3154
3155 // requireAuth is recursive if the issuer is a vault pseudo-account
3156 auto const sleIssuer = view.read(keylet::account(mptIssuer));
3157 if (!sleIssuer)
3158 return tefINTERNAL; // LCOV_EXCL_LINE
3159
3160 if (sleIssuer->isFieldPresent(sfVaultID))
3161 {
3162 auto const sleVault =
3163 view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
3164 if (!sleVault)
3165 return tefINTERNAL; // LCOV_EXCL_LINE
3166
3167 auto const asset = sleVault->at(sfAsset);
3168 if (auto const err = std::visit(
3169 [&]<ValidIssueType TIss>(TIss const& issue) {
3170 if constexpr (std::is_same_v<TIss, Issue>)
3171 return requireAuth(view, issue, account, authType);
3172 else
3173 return requireAuth(
3174 view, issue, account, authType, depth + 1);
3175 },
3176 asset.value());
3177 !isTesSuccess(err))
3178 return err;
3179 }
3180 }
3181
3182 auto const mptokenID = keylet::mptoken(mptID.key, account);
3183 auto const sleToken = view.read(mptokenID);
3184
3185 // if account has no MPToken, fail
3186 if (!sleToken &&
3187 (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
3188 return tecNO_AUTH;
3189
3190 // Note, this check is not amendment-gated because DomainID will be always
3191 // empty **unless** writing to it has been enabled by an amendment
3192 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
3193 if (maybeDomainID)
3194 {
3195 XRPL_ASSERT(
3196 sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
3197 "ripple::requireAuth : issuance requires authorization");
3198 // ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
3199 if (auto const ter =
3200 credentials::validDomain(view, *maybeDomainID, account);
3201 isTesSuccess(ter))
3202 return ter; // Note: sleToken might be null
3203 else if (!sleToken)
3204 return ter;
3205 // We ignore error from validDomain if we found sleToken, as it could
3206 // belong to someone who is explicitly authorized e.g. a vault owner.
3207 }
3208
3209 if (featureSAVEnabled)
3210 {
3211 // Implicitly authorize Vault and LoanBroker pseudo-accounts
3212 if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
3213 return tesSUCCESS;
3214 }
3215
3216 // mptoken must be authorized if issuance enabled requireAuth
3217 if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
3218 (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
3219 return tecNO_AUTH;
3220
3221 return tesSUCCESS; // Note: sleToken might be null
3222}
3223
3224[[nodiscard]] TER
3226 ApplyView& view,
3227 MPTID const& mptIssuanceID,
3228 AccountID const& account,
3229 XRPAmount const& priorBalance, // for MPToken authorization
3231{
3232 auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
3233 if (!sleIssuance)
3234 return tefINTERNAL; // LCOV_EXCL_LINE
3235
3236 XRPL_ASSERT(
3237 sleIssuance->isFlag(lsfMPTRequireAuth),
3238 "ripple::enforceMPTokenAuthorization : authorization required");
3239
3240 if (account == sleIssuance->at(sfIssuer))
3241 return tefINTERNAL; // LCOV_EXCL_LINE
3242
3243 auto const keylet = keylet::mptoken(mptIssuanceID, account);
3244 auto const sleToken = view.read(keylet); // NOTE: might be null
3245 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
3246 bool expired = false;
3247 bool const authorizedByDomain = [&]() -> bool {
3248 // NOTE: defensive here, shuld be checked in preclaim
3249 if (!maybeDomainID.has_value())
3250 return false; // LCOV_EXCL_LINE
3251
3252 auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
3253 if (isTesSuccess(ter))
3254 return true;
3255 if (ter == tecEXPIRED)
3256 expired = true;
3257 return false;
3258 }();
3259
3260 if (!authorizedByDomain && sleToken == nullptr)
3261 {
3262 // Could not find MPToken and won't create one, could be either of:
3263 //
3264 // 1. Field sfDomainID not set in MPTokenIssuance or
3265 // 2. Account has no matching and accepted credentials or
3266 // 3. Account has all expired credentials (deleted in verifyValidDomain)
3267 //
3268 // Either way, return tecNO_AUTH and there is nothing else to do
3269 return expired ? tecEXPIRED : tecNO_AUTH;
3270 }
3271 else if (!authorizedByDomain && maybeDomainID.has_value())
3272 {
3273 // Found an MPToken but the account is not authorized and we expect
3274 // it to have been authorized by the domain. This could be because the
3275 // credentials used to create the MPToken have expired or been deleted.
3276 return expired ? tecEXPIRED : tecNO_AUTH;
3277 }
3278 else if (!authorizedByDomain)
3279 {
3280 // We found an MPToken, but sfDomainID is not set, so this is a classic
3281 // MPToken which requires authorization by the token issuer.
3282 XRPL_ASSERT(
3283 sleToken != nullptr && !maybeDomainID.has_value(),
3284 "ripple::enforceMPTokenAuthorization : found MPToken");
3285 if (sleToken->isFlag(lsfMPTAuthorized))
3286 return tesSUCCESS;
3287
3288 return tecNO_AUTH;
3289 }
3290 else if (authorizedByDomain && sleToken != nullptr)
3291 {
3292 // Found an MPToken, authorized by the domain. Ignore authorization flag
3293 // lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
3294 XRPL_ASSERT(
3295 maybeDomainID.has_value(),
3296 "ripple::enforceMPTokenAuthorization : found MPToken for domain");
3297 return tesSUCCESS;
3298 }
3299 else if (authorizedByDomain)
3300 {
3301 // Could not find MPToken but there should be one because we are
3302 // authorized by domain. Proceed to create it, then return tesSUCCESS
3303 XRPL_ASSERT(
3304 maybeDomainID.has_value() && sleToken == nullptr,
3305 "ripple::enforceMPTokenAuthorization : new MPToken for domain");
3306 if (auto const err = authorizeMPToken(
3307 view,
3308 priorBalance, // priorBalance
3309 mptIssuanceID, // mptIssuanceID
3310 account, // account
3311 j);
3312 !isTesSuccess(err))
3313 return err;
3314
3315 return tesSUCCESS;
3316 }
3317
3318 // LCOV_EXCL_START
3319 UNREACHABLE(
3320 "ripple::enforceMPTokenAuthorization : condition list is incomplete");
3321 return tefINTERNAL;
3322 // LCOV_EXCL_STOP
3323}
3324
3325TER
3327 ReadView const& view,
3328 MPTIssue const& mptIssue,
3329 AccountID const& from,
3330 AccountID const& to)
3331{
3332 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3333 auto const sleIssuance = view.read(mptID);
3334 if (!sleIssuance)
3335 return tecOBJECT_NOT_FOUND;
3336
3337 if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
3338 {
3339 if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
3340 return TER{tecNO_AUTH};
3341 }
3342 return tesSUCCESS;
3343}
3344
3345[[nodiscard]] TER
3347 ReadView const& view,
3348 Issue const& issue,
3349 AccountID const& from,
3350 AccountID const& to)
3351{
3352 if (issue.native())
3353 return tesSUCCESS;
3354
3355 auto const& issuerId = issue.getIssuer();
3356 if (issuerId == from || issuerId == to)
3357 return tesSUCCESS;
3358 auto const sleIssuer = view.read(keylet::account(issuerId));
3359 if (sleIssuer == nullptr)
3360 return tefINTERNAL; // LCOV_EXCL_LINE
3361
3362 auto const isRippleDisabled = [&](AccountID account) -> bool {
3363 // Line might not exist, but some transfers can create it. If this
3364 // is the case, just check the default ripple on the issuer account.
3365 auto const line = view.read(keylet::line(account, issue));
3366 if (line)
3367 {
3368 bool const issuerHigh = issuerId > account;
3369 return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
3370 }
3371 return sleIssuer->isFlag(lsfDefaultRipple) == false;
3372 };
3373
3374 // Fail if rippling disabled on both trust lines
3375 if (isRippleDisabled(from) && isRippleDisabled(to))
3376 return terNO_RIPPLE;
3377
3378 return tesSUCCESS;
3379}
3380
3381TER
3383 ApplyView& view,
3384 Keylet const& ownerDirKeylet,
3385 EntryDeleter const& deleter,
3387 std::optional<uint16_t> maxNodesToDelete)
3388{
3389 // Delete all the entries in the account directory.
3390 std::shared_ptr<SLE> sleDirNode{};
3391 unsigned int uDirEntry{0};
3392 uint256 dirEntry{beast::zero};
3393 std::uint32_t deleted = 0;
3394
3395 if (view.exists(ownerDirKeylet) &&
3396 dirFirst(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
3397 {
3398 do
3399 {
3400 if (maxNodesToDelete && ++deleted > *maxNodesToDelete)
3401 return tecINCOMPLETE;
3402
3403 // Choose the right way to delete each directory node.
3404 auto sleItem = view.peek(keylet::child(dirEntry));
3405 if (!sleItem)
3406 {
3407 // Directory node has an invalid index. Bail out.
3408 // LCOV_EXCL_START
3409 JLOG(j.fatal())
3410 << "DeleteAccount: Directory node in ledger " << view.seq()
3411 << " has index to object that is missing: "
3412 << to_string(dirEntry);
3413 return tefBAD_LEDGER;
3414 // LCOV_EXCL_STOP
3415 }
3416
3417 LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
3418 sleItem->getFieldU16(sfLedgerEntryType))};
3419
3420 // Deleter handles the details of specific account-owned object
3421 // deletion
3422 auto const [ter, skipEntry] = deleter(nodeType, dirEntry, sleItem);
3423 if (ter != tesSUCCESS)
3424 return ter;
3425
3426 // dirFirst() and dirNext() are like iterators with exposed
3427 // internal state. We'll take advantage of that exposed state
3428 // to solve a common C++ problem: iterator invalidation while
3429 // deleting elements from a container.
3430 //
3431 // We have just deleted one directory entry, which means our
3432 // "iterator state" is invalid.
3433 //
3434 // 1. During the process of getting an entry from the
3435 // directory uDirEntry was incremented from 'it' to 'it'+1.
3436 //
3437 // 2. We then deleted the entry at index 'it', which means the
3438 // entry that was at 'it'+1 has now moved to 'it'.
3439 //
3440 // 3. So we verify that uDirEntry is indeed 'it'+1. Then we jam it
3441 // back to 'it' to "un-invalidate" the iterator.
3442 XRPL_ASSERT(
3443 uDirEntry >= 1,
3444 "ripple::cleanupOnAccountDelete : minimum dir entries");
3445 if (uDirEntry == 0)
3446 {
3447 // LCOV_EXCL_START
3448 JLOG(j.error())
3449 << "DeleteAccount iterator re-validation failed.";
3450 return tefBAD_LEDGER;
3451 // LCOV_EXCL_STOP
3452 }
3453 if (skipEntry == SkipEntry::No)
3454 uDirEntry--;
3455
3456 } while (
3457 dirNext(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
3458 }
3459
3460 return tesSUCCESS;
3461}
3462
3463TER
3465 ApplyView& view,
3466 std::shared_ptr<SLE> sleState,
3467 std::optional<AccountID> const& ammAccountID,
3469{
3470 if (!sleState || sleState->getType() != ltRIPPLE_STATE)
3471 return tecINTERNAL; // LCOV_EXCL_LINE
3472
3473 auto const& [low, high] = std::minmax(
3474 sleState->getFieldAmount(sfLowLimit).getIssuer(),
3475 sleState->getFieldAmount(sfHighLimit).getIssuer());
3476 auto sleLow = view.peek(keylet::account(low));
3477 auto sleHigh = view.peek(keylet::account(high));
3478 if (!sleLow || !sleHigh)
3479 return tecINTERNAL; // LCOV_EXCL_LINE
3480
3481 bool const ammLow = sleLow->isFieldPresent(sfAMMID);
3482 bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
3483
3484 // can't both be AMM
3485 if (ammLow && ammHigh)
3486 return tecINTERNAL; // LCOV_EXCL_LINE
3487
3488 // at least one must be
3489 if (!ammLow && !ammHigh)
3490 return terNO_AMM;
3491
3492 // one must be the target amm
3493 if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
3494 return terNO_AMM;
3495
3496 if (auto const ter = trustDelete(view, sleState, low, high, j);
3497 ter != tesSUCCESS)
3498 {
3499 JLOG(j.error())
3500 << "deleteAMMTrustLine: failed to delete the trustline.";
3501 return ter;
3502 }
3503
3504 auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
3505 if (!(sleState->getFlags() & uFlags))
3506 return tecINTERNAL; // LCOV_EXCL_LINE
3507
3508 adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
3509
3510 return tesSUCCESS;
3511}
3512
3513TER
3515 ApplyView& view,
3516 AccountID const& uSenderID,
3517 AccountID const& uReceiverID,
3518 STAmount const& saAmount,
3519 bool bCheckIssuer,
3521{
3522 return std::visit(
3523 [&]<ValidIssueType TIss>(TIss const& issue) {
3524 if constexpr (std::is_same_v<TIss, Issue>)
3525 {
3526 return rippleCreditIOU(
3527 view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
3528 }
3529 else
3530 {
3531 XRPL_ASSERT(
3532 !bCheckIssuer,
3533 "ripple::rippleCredit : not checking issuer");
3534 return rippleCreditMPT(
3535 view, uSenderID, uReceiverID, saAmount, j);
3536 }
3537 },
3538 saAmount.asset().value());
3539}
3540
3541[[nodiscard]] std::optional<STAmount>
3543 std::shared_ptr<SLE const> const& vault,
3544 std::shared_ptr<SLE const> const& issuance,
3545 STAmount const& assets)
3546{
3547 XRPL_ASSERT(
3548 !assets.negative(),
3549 "ripple::assetsToSharesDeposit : non-negative assets");
3550 XRPL_ASSERT(
3551 assets.asset() == vault->at(sfAsset),
3552 "ripple::assetsToSharesDeposit : assets and vault match");
3553 if (assets.negative() || assets.asset() != vault->at(sfAsset))
3554 return std::nullopt; // LCOV_EXCL_LINE
3555
3556 Number const assetTotal = vault->at(sfAssetsTotal);
3557 STAmount shares{vault->at(sfShareMPTID)};
3558 if (assetTotal == 0)
3559 return STAmount{
3560 shares.asset(),
3561 Number(assets.mantissa(), assets.exponent() + vault->at(sfScale))
3562 .truncate()};
3563
3564 Number const shareTotal = issuance->at(sfOutstandingAmount);
3565 shares = ((shareTotal * assets) / assetTotal).truncate();
3566 return shares;
3567}
3568
3569[[nodiscard]] std::optional<STAmount>
3571 std::shared_ptr<SLE const> const& vault,
3572 std::shared_ptr<SLE const> const& issuance,
3573 STAmount const& shares)
3574{
3575 XRPL_ASSERT(
3576 !shares.negative(),
3577 "ripple::sharesToAssetsDeposit : non-negative shares");
3578 XRPL_ASSERT(
3579 shares.asset() == vault->at(sfShareMPTID),
3580 "ripple::sharesToAssetsDeposit : shares and vault match");
3581 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
3582 return std::nullopt; // LCOV_EXCL_LINE
3583
3584 Number const assetTotal = vault->at(sfAssetsTotal);
3585 STAmount assets{vault->at(sfAsset)};
3586 if (assetTotal == 0)
3587 return STAmount{
3588 assets.asset(),
3589 shares.mantissa(),
3590 shares.exponent() - vault->at(sfScale),
3591 false};
3592
3593 Number const shareTotal = issuance->at(sfOutstandingAmount);
3594 assets = (assetTotal * shares) / shareTotal;
3595 return assets;
3596}
3597
3598[[nodiscard]] std::optional<STAmount>
3600 std::shared_ptr<SLE const> const& vault,
3601 std::shared_ptr<SLE const> const& issuance,
3602 STAmount const& assets,
3603 TruncateShares truncate)
3604{
3605 XRPL_ASSERT(
3606 !assets.negative(),
3607 "ripple::assetsToSharesDeposit : non-negative assets");
3608 XRPL_ASSERT(
3609 assets.asset() == vault->at(sfAsset),
3610 "ripple::assetsToSharesWithdraw : assets and vault match");
3611 if (assets.negative() || assets.asset() != vault->at(sfAsset))
3612 return std::nullopt; // LCOV_EXCL_LINE
3613
3614 Number assetTotal = vault->at(sfAssetsTotal);
3615 assetTotal -= vault->at(sfLossUnrealized);
3616 STAmount shares{vault->at(sfShareMPTID)};
3617 if (assetTotal == 0)
3618 return shares;
3619 Number const shareTotal = issuance->at(sfOutstandingAmount);
3620 Number result = (shareTotal * assets) / assetTotal;
3621 if (truncate == TruncateShares::yes)
3622 result = result.truncate();
3623 shares = result;
3624 return shares;
3625}
3626
3627[[nodiscard]] std::optional<STAmount>
3629 std::shared_ptr<SLE const> const& vault,
3630 std::shared_ptr<SLE const> const& issuance,
3631 STAmount const& shares)
3632{
3633 XRPL_ASSERT(
3634 !shares.negative(),
3635 "ripple::sharesToAssetsDeposit : non-negative shares");
3636 XRPL_ASSERT(
3637 shares.asset() == vault->at(sfShareMPTID),
3638 "ripple::sharesToAssetsWithdraw : shares and vault match");
3639 if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
3640 return std::nullopt; // LCOV_EXCL_LINE
3641
3642 Number assetTotal = vault->at(sfAssetsTotal);
3643 assetTotal -= vault->at(sfLossUnrealized);
3644 STAmount assets{vault->at(sfAsset)};
3645 if (assetTotal == 0)
3646 return assets;
3647 Number const shareTotal = issuance->at(sfOutstandingAmount);
3648 assets = (assetTotal * shares) / shareTotal;
3649 return assets;
3650}
3651
3652TER
3654 ApplyView& view,
3655 AccountID const& sender,
3656 STAmount const& amount,
3658{
3659 auto const mptIssue = amount.get<MPTIssue>();
3660 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3661 auto sleIssuance = view.peek(mptID);
3662 if (!sleIssuance)
3663 { // LCOV_EXCL_START
3664 JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
3665 << mptIssue.getMptID();
3666 return tecOBJECT_NOT_FOUND;
3667 } // LCOV_EXCL_STOP
3668
3669 if (amount.getIssuer() == sender)
3670 { // LCOV_EXCL_START
3671 JLOG(j.error())
3672 << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
3673 return tecINTERNAL;
3674 } // LCOV_EXCL_STOP
3675
3676 // 1. Decrease the MPT Holder MPTAmount
3677 // 2. Increase the MPT Holder EscrowedAmount
3678 {
3679 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3680 auto sle = view.peek(mptokenID);
3681 if (!sle)
3682 { // LCOV_EXCL_START
3683 JLOG(j.error())
3684 << "rippleLockEscrowMPT: MPToken not found for " << sender;
3685 return tecOBJECT_NOT_FOUND;
3686 } // LCOV_EXCL_STOP
3687
3688 auto const amt = sle->getFieldU64(sfMPTAmount);
3689 auto const pay = amount.mpt().value();
3690
3691 // Underflow check for subtraction
3692 if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
3693 { // LCOV_EXCL_START
3694 JLOG(j.error())
3695 << "rippleLockEscrowMPT: insufficient MPTAmount for "
3696 << to_string(sender) << ": " << amt << " < " << pay;
3697 return tecINTERNAL;
3698 } // LCOV_EXCL_STOP
3699
3700 (*sle)[sfMPTAmount] = amt - pay;
3701
3702 // Overflow check for addition
3703 uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
3704
3705 if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
3706 { // LCOV_EXCL_START
3707 JLOG(j.error())
3708 << "rippleLockEscrowMPT: overflow on locked amount for "
3709 << to_string(sender) << ": " << locked << " + " << pay;
3710 return tecINTERNAL;
3711 } // LCOV_EXCL_STOP
3712
3713 if (sle->isFieldPresent(sfLockedAmount))
3714 (*sle)[sfLockedAmount] += pay;
3715 else
3716 sle->setFieldU64(sfLockedAmount, pay);
3717
3718 view.update(sle);
3719 }
3720
3721 // 1. Increase the Issuance EscrowedAmount
3722 // 2. DO NOT change the Issuance OutstandingAmount
3723 {
3724 uint64_t const issuanceEscrowed =
3725 (*sleIssuance)[~sfLockedAmount].value_or(0);
3726 auto const pay = amount.mpt().value();
3727
3728 // Overflow check for addition
3729 if (!canAdd(
3730 STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
3731 { // LCOV_EXCL_START
3732 JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
3733 "locked amount for "
3734 << mptIssue.getMptID() << ": " << issuanceEscrowed
3735 << " + " << pay;
3736 return tecINTERNAL;
3737 } // LCOV_EXCL_STOP
3738
3739 if (sleIssuance->isFieldPresent(sfLockedAmount))
3740 (*sleIssuance)[sfLockedAmount] += pay;
3741 else
3742 sleIssuance->setFieldU64(sfLockedAmount, pay);
3743
3744 view.update(sleIssuance);
3745 }
3746 return tesSUCCESS;
3747}
3748
3749TER
3751 ApplyView& view,
3752 AccountID const& sender,
3753 AccountID const& receiver,
3754 STAmount const& netAmount,
3755 STAmount const& grossAmount,
3757{
3758 if (!view.rules().enabled(fixTokenEscrowV1))
3759 XRPL_ASSERT(
3760 netAmount == grossAmount,
3761 "ripple::rippleUnlockEscrowMPT : netAmount == grossAmount");
3762
3763 auto const& issuer = netAmount.getIssuer();
3764 auto const& mptIssue = netAmount.get<MPTIssue>();
3765 auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
3766 auto sleIssuance = view.peek(mptID);
3767 if (!sleIssuance)
3768 { // LCOV_EXCL_START
3769 JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
3770 << mptIssue.getMptID();
3771 return tecOBJECT_NOT_FOUND;
3772 } // LCOV_EXCL_STOP
3773
3774 // Decrease the Issuance EscrowedAmount
3775 {
3776 if (!sleIssuance->isFieldPresent(sfLockedAmount))
3777 { // LCOV_EXCL_START
3778 JLOG(j.error())
3779 << "rippleUnlockEscrowMPT: no locked amount in issuance for "
3780 << mptIssue.getMptID();
3781 return tecINTERNAL;
3782 } // LCOV_EXCL_STOP
3783
3784 auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
3785 auto const redeem = grossAmount.mpt().value();
3786
3787 // Underflow check for subtraction
3788 if (!canSubtract(
3789 STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
3790 { // LCOV_EXCL_START
3791 JLOG(j.error())
3792 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3793 << mptIssue.getMptID() << ": " << locked << " < " << redeem;
3794 return tecINTERNAL;
3795 } // LCOV_EXCL_STOP
3796
3797 auto const newLocked = locked - redeem;
3798 if (newLocked == 0)
3799 sleIssuance->makeFieldAbsent(sfLockedAmount);
3800 else
3801 sleIssuance->setFieldU64(sfLockedAmount, newLocked);
3802 view.update(sleIssuance);
3803 }
3804
3805 if (issuer != receiver)
3806 {
3807 // Increase the MPT Holder MPTAmount
3808 auto const mptokenID = keylet::mptoken(mptID.key, receiver);
3809 auto sle = view.peek(mptokenID);
3810 if (!sle)
3811 { // LCOV_EXCL_START
3812 JLOG(j.error())
3813 << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
3814 return tecOBJECT_NOT_FOUND;
3815 } // LCOV_EXCL_STOP
3816
3817 auto current = sle->getFieldU64(sfMPTAmount);
3818 auto delta = netAmount.mpt().value();
3819
3820 // Overflow check for addition
3821 if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
3822 { // LCOV_EXCL_START
3823 JLOG(j.error())
3824 << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
3825 << to_string(receiver) << ": " << current << " + " << delta;
3826 return tecINTERNAL;
3827 } // LCOV_EXCL_STOP
3828
3829 (*sle)[sfMPTAmount] += delta;
3830 view.update(sle);
3831 }
3832 else
3833 {
3834 // Decrease the Issuance OutstandingAmount
3835 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3836 auto const redeem = netAmount.mpt().value();
3837
3838 // Underflow check for subtraction
3839 if (!canSubtract(
3840 STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
3841 { // LCOV_EXCL_START
3842 JLOG(j.error())
3843 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3844 << mptIssue.getMptID() << ": " << outstanding << " < "
3845 << redeem;
3846 return tecINTERNAL;
3847 } // LCOV_EXCL_STOP
3848
3849 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
3850 view.update(sleIssuance);
3851 }
3852
3853 if (issuer == sender)
3854 { // LCOV_EXCL_START
3855 JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
3856 "cannot unlock MPTs.";
3857 return tecINTERNAL;
3858 } // LCOV_EXCL_STOP
3859 else
3860 {
3861 // Decrease the MPT Holder EscrowedAmount
3862 auto const mptokenID = keylet::mptoken(mptID.key, sender);
3863 auto sle = view.peek(mptokenID);
3864 if (!sle)
3865 { // LCOV_EXCL_START
3866 JLOG(j.error())
3867 << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
3868 return tecOBJECT_NOT_FOUND;
3869 } // LCOV_EXCL_STOP
3870
3871 if (!sle->isFieldPresent(sfLockedAmount))
3872 { // LCOV_EXCL_START
3873 JLOG(j.error())
3874 << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
3875 << to_string(sender);
3876 return tecINTERNAL;
3877 } // LCOV_EXCL_STOP
3878
3879 auto const locked = sle->getFieldU64(sfLockedAmount);
3880 auto const delta = grossAmount.mpt().value();
3881
3882 // Underflow check for subtraction
3883 if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
3884 { // LCOV_EXCL_START
3885 JLOG(j.error())
3886 << "rippleUnlockEscrowMPT: insufficient locked amount for "
3887 << to_string(sender) << ": " << locked << " < " << delta;
3888 return tecINTERNAL;
3889 } // LCOV_EXCL_STOP
3890
3891 auto const newLocked = locked - delta;
3892 if (newLocked == 0)
3893 sle->makeFieldAbsent(sfLockedAmount);
3894 else
3895 sle->setFieldU64(sfLockedAmount, newLocked);
3896 view.update(sle);
3897 }
3898
3899 // Note: The gross amount is the amount that was locked, the net
3900 // amount is the amount that is being unlocked. The difference is the fee
3901 // that was charged for the transfer. If this difference is greater than
3902 // zero, we need to update the outstanding amount.
3903 auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
3904 if (diff != 0)
3905 {
3906 auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
3907 // Underflow check for subtraction
3908 if (!canSubtract(
3909 STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
3910 { // LCOV_EXCL_START
3911 JLOG(j.error())
3912 << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
3913 << mptIssue.getMptID() << ": " << outstanding << " < " << diff;
3914 return tecINTERNAL;
3915 } // LCOV_EXCL_STOP
3916
3917 sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
3918 view.update(sleIssuance);
3919 }
3920 return tesSUCCESS;
3921}
3922
3923bool
3925{
3926 return now.time_since_epoch().count() > mark;
3927}
3928
3929} // 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 LedgerHeader const & header() const =0
Returns information about the ledger.
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 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:2799
AccountID const & noAccount()
A placeholder for empty accounts.
static bool updateTrustLine(ApplyView &view, SLE::pointer state, bool bSenderHigh, AccountID const &sender, STAmount const &before, STAmount const &after, beast::Journal j)
Definition View.cpp:2824
static TER accountSendMultiIOU(ApplyView &view, AccountID const &senderID, Issue const &issue, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2376
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:1227
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:2174
TER checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
Validates that the destination SLE and tag are valid.
Definition View.cpp:1333
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:3653
@ 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:1391
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:3570
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:2576
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:2977
@ 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:1347
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:3542
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:3599
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:3052
@ 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:2778
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:3750
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:3097
@ 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:3628
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:1965
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:3326
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:1247
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:2877
static TER accountSendIOU(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2252
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:2642
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:1864
@ 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:2763
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:3514
@ 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:1440
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:3924
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1636
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:3464
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:3225
TER canAddHolding(ReadView const &view, Asset const &asset)
Definition View.cpp:1323
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:2119
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:1200
@ 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:1516
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:1763
static TER rippleCreditMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition View.cpp:2514
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:1904
static TER accountSendMPT(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition View.cpp:2738
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)