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