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