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