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