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