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