rippled
Loading...
Searching...
No Matches
XChainBridge.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2022 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/paths/Flow.h>
21#include <xrpld/app/tx/detail/SignerEntries.h>
22#include <xrpld/app/tx/detail/Transactor.h>
23#include <xrpld/app/tx/detail/XChainBridge.h>
24
25#include <xrpl/basics/Log.h>
26#include <xrpl/basics/Number.h>
27#include <xrpl/basics/chrono.h>
28#include <xrpl/beast/utility/Journal.h>
29#include <xrpl/beast/utility/instrumentation.h>
30#include <xrpl/ledger/ApplyView.h>
31#include <xrpl/ledger/PaymentSandbox.h>
32#include <xrpl/ledger/View.h>
33#include <xrpl/protocol/AccountID.h>
34#include <xrpl/protocol/Feature.h>
35#include <xrpl/protocol/Indexes.h>
36#include <xrpl/protocol/PublicKey.h>
37#include <xrpl/protocol/SField.h>
38#include <xrpl/protocol/STAmount.h>
39#include <xrpl/protocol/STObject.h>
40#include <xrpl/protocol/STXChainBridge.h>
41#include <xrpl/protocol/TER.h>
42#include <xrpl/protocol/TxFlags.h>
43#include <xrpl/protocol/XChainAttestations.h>
44#include <xrpl/protocol/XRPAmount.h>
45
46#include <unordered_map>
47#include <unordered_set>
48
49namespace ripple {
50
51/*
52 Bridges connect two independent ledgers: a "locking chain" and an "issuing
53 chain". An asset can be moved from the locking chain to the issuing chain by
54 putting it into trust on the locking chain, and issuing a "wrapped asset"
55 that represents the locked asset on the issuing chain.
56
57 Note that a bridge is not an exchange. There is no exchange rate: one wrapped
58 asset on the issuing chain always represents one asset in trust on the
59 locking chain. The bridge also does not exchange an asset on the locking
60 chain for an asset on the issuing chain.
61
62 A good model for thinking about bridges is a box that contains an infinite
63 number of "wrapped tokens". When a token from the locking chain
64 (locking-chain-token) is put into the box, a wrapped token is taken out of
65 the box and put onto the issuing chain (issuing-chain-token). No one can use
66 the locking-chain-token while it remains in the box. When an
67 issuing-chain-token is returned to the box, one locking-chain-token is taken
68 out of the box and put back onto the locking chain.
69
70 This requires a way to put assets into trust on one chain (put a
71 locking-chain-token into the box). A regular XRP account is used for this.
72 This account is called a "door account". Much in the same way that a door is
73 used to go from one room to another, a door account is used to move from one
74 chain to another. This account will be jointly controlled by a set of witness
75 servers by using the ledger's multi-signature support. The master key will be
76 disabled. These witness servers are trusted in the sense that if a quorum of
77 them collude, they can steal the funds put into trust.
78
79 This also requires a way to prove that assets were put into the box - either
80 a locking-chain-token on the locking chain or returning an
81 issuing-chain-token on the issuing chain. A set of servers called "witness
82 servers" fill this role. These servers watch the ledger for these
83 transactions, and attest that the given events happened on the different
84 chains by signing messages with the event information.
85
86 There needs to be a way to prevent the attestations from the witness
87 servers from being used more than once. "Claim ids" fill this role. A claim
88 id must be acquired on the destination chain before the asset is "put into
89 the box" on the source chain. This claim id has a unique id, and once it is
90 destroyed it can never exist again (it's a simple counter). The attestations
91 reference this claim id, and are accumulated on the claim id. Once a quorum
92 is reached, funds can move. Once the funds move, the claim id is destroyed.
93
94 Finally, a claim id requires that the sender has an account on the
95 destination chain. For some chains, this can be a problem - especially if
96 the wrapped asset represents XRP, and XRP is needed to create an account.
97 There's a bootstrap problem. To address this, there is a special transaction
98 used to create accounts. This transaction does not require a claim id.
99
100 See the document "docs/bridge/spec.md" for a full description of how
101 bridges and their transactions work.
102*/
103
104namespace {
105
106// Check that the public key is allowed to sign for the given account. If the
107// account does not exist on the ledger, then the public key must be the master
108// key for the given account if it existed. Otherwise the key must be an enabled
109// master key or a regular key for the existing account.
110TER
111checkAttestationPublicKey(
112 ReadView const& view,
114 AccountID const& attestationSignerAccount,
115 PublicKey const& pk,
117{
118 if (!signersList.contains(attestationSignerAccount))
119 {
120 return tecNO_PERMISSION;
121 }
122
123 AccountID const accountFromPK = calcAccountID(pk);
124
125 if (auto const sleAttestationSigningAccount =
126 view.read(keylet::account(attestationSignerAccount)))
127 {
128 if (accountFromPK == attestationSignerAccount)
129 {
130 // master key
131 if (sleAttestationSigningAccount->getFieldU32(sfFlags) &
133 {
134 JLOG(j.trace()) << "Attempt to add an attestation with "
135 "disabled master key.";
137 }
138 }
139 else
140 {
141 // regular key
142 if (std::optional<AccountID> regularKey =
143 (*sleAttestationSigningAccount)[~sfRegularKey];
144 regularKey != accountFromPK)
145 {
146 if (!regularKey)
147 {
148 JLOG(j.trace())
149 << "Attempt to add an attestation with "
150 "account present and non-present regular key.";
151 }
152 else
153 {
154 JLOG(j.trace()) << "Attempt to add an attestation with "
155 "account present and mismatched "
156 "regular key/public key.";
157 }
159 }
160 }
161 }
162 else
163 {
164 // account does not exist.
165 if (calcAccountID(pk) != attestationSignerAccount)
166 {
167 JLOG(j.trace())
168 << "Attempt to add an attestation with non-existant account "
169 "and mismatched pk/account pair.";
171 }
172 }
173
174 return tesSUCCESS;
175}
176
177// If there is a quorum of attestations for the given parameters, then
178// return the reward accounts, otherwise return TER for the error.
179// Also removes attestations that are no longer part of the signers list.
180//
181// Note: the dst parameter is what the attestations are attesting to, which
182// is not always used (it is used when automatically triggering a transfer
183// from an `addAttestation` transaction, it is not used in a `claim`
184// transaction). If the `checkDst` parameter is `check`, the attestations
185// must attest to this destination, if it is `ignore` then the `dst` of the
186// attestations are not checked (as for a `claim` transaction)
187
188enum class CheckDst { check, ignore };
189template <class TAttestation>
190Expected<std::vector<AccountID>, TER>
191claimHelper(
192 XChainAttestationsBase<TAttestation>& attestations,
193 ReadView const& view,
194 typename TAttestation::MatchFields const& toMatch,
195 CheckDst checkDst,
196 std::uint32_t quorum,
199{
200 // Remove attestations that are not valid signers. They may be no longer
201 // part of the signers list, or their master key may have been disabled,
202 // or their regular key may have changed
203 attestations.erase_if([&](auto const& a) {
204 return checkAttestationPublicKey(
205 view, signersList, a.keyAccount, a.publicKey, j) !=
207 });
208
209 // Check if we have quorum for the amount specified on the new claimAtt
210 std::vector<AccountID> rewardAccounts;
211 rewardAccounts.reserve(attestations.size());
212 std::uint32_t weight = 0;
213 for (auto const& a : attestations)
214 {
215 auto const matchR = a.match(toMatch);
216 // The dest must match if claimHelper is being run as a result of an add
217 // attestation transaction. The dst does not need to match if the
218 // claimHelper is being run using an explicit claim transaction.
219 using enum AttestationMatch;
220 if (matchR == nonDstMismatch ||
221 (checkDst == CheckDst::check && matchR != match))
222 continue;
223 auto i = signersList.find(a.keyAccount);
224 if (i == signersList.end())
225 {
226 // LCOV_EXCL_START
227 UNREACHABLE(
228 "ripple::claimHelper : invalid inputs"); // should have already
229 // been checked
230 continue;
231 // LCOV_EXCL_STOP
232 }
233 weight += i->second;
234 rewardAccounts.push_back(a.rewardAccount);
235 }
236
237 if (weight >= quorum)
238 return rewardAccounts;
239
241}
242
274struct OnNewAttestationResult
275{
277 // `changed` is true if the attestation collection changed in any way
278 // (added/removed/changed)
279 bool changed{false};
280};
281
282template <class TAttestation>
283[[nodiscard]] OnNewAttestationResult
284onNewAttestations(
285 XChainAttestationsBase<TAttestation>& attestations,
286 ReadView const& view,
287 typename TAttestation::TSignedAttestation const* attBegin,
288 typename TAttestation::TSignedAttestation const* attEnd,
289 std::uint32_t quorum,
292{
293 bool changed = false;
294 for (auto att = attBegin; att != attEnd; ++att)
295 {
296 if (checkAttestationPublicKey(
297 view,
298 signersList,
299 att->attestationSignerAccount,
300 att->publicKey,
301 j) != tesSUCCESS)
302 {
303 // The checkAttestationPublicKey is not strictly necessary here (it
304 // should be checked in a preclaim step), but it would be bad to let
305 // this slip through if that changes, and the check is relatively
306 // cheap, so we check again
307 continue;
308 }
309
310 auto const& claimSigningAccount = att->attestationSignerAccount;
311 if (auto i = std::find_if(
312 attestations.begin(),
313 attestations.end(),
314 [&](auto const& a) {
315 return a.keyAccount == claimSigningAccount;
316 });
317 i != attestations.end())
318 {
319 // existing attestation
320 // replace old attestation with new attestation
321 *i = TAttestation{*att};
322 changed = true;
323 }
324 else
325 {
326 attestations.emplace_back(*att);
327 changed = true;
328 }
329 }
330
331 auto r = claimHelper(
332 attestations,
333 view,
334 typename TAttestation::MatchFields{*attBegin},
335 CheckDst::check,
336 quorum,
337 signersList,
338 j);
339
340 if (!r.has_value())
341 return {std::nullopt, changed};
342
343 return {std::move(r.value()), changed};
344};
345
346// Check if there is a quorurm of attestations for the given amount and
347// chain. If so return the reward accounts, if not return the tec code (most
348// likely tecXCHAIN_CLAIM_NO_QUORUM)
349Expected<std::vector<AccountID>, TER>
350onClaim(
351 XChainClaimAttestations& attestations,
352 ReadView const& view,
353 STAmount const& sendingAmount,
354 bool wasLockingChainSend,
355 std::uint32_t quorum,
358{
359 XChainClaimAttestation::MatchFields toMatch{
360 sendingAmount, wasLockingChainSend, std::nullopt};
361 return claimHelper(
362 attestations, view, toMatch, CheckDst::ignore, quorum, signersList, j);
363}
364
365enum class CanCreateDstPolicy { no, yes };
366
367enum class DepositAuthPolicy { normal, dstCanBypass };
368
369// Allow the fee to dip into the reserve. To support this, information about the
370// submitting account needs to be fed to the transfer helper.
371struct TransferHelperSubmittingAccountInfo
372{
373 AccountID account;
374 STAmount preFeeBalance;
375 STAmount postFeeBalance;
376};
377
400TER
401transferHelper(
402 PaymentSandbox& psb,
403 AccountID const& src,
404 AccountID const& dst,
405 std::optional<std::uint32_t> const& dstTag,
406 std::optional<AccountID> const& claimOwner,
407 STAmount const& amt,
408 CanCreateDstPolicy canCreate,
409 DepositAuthPolicy depositAuthPolicy,
411 submittingAccountInfo,
413{
414 if (dst == src)
415 return tesSUCCESS;
416
417 auto const dstK = keylet::account(dst);
418 if (auto sleDst = psb.read(dstK))
419 {
420 // Check dst tag and deposit auth
421
422 if ((sleDst->getFlags() & lsfRequireDestTag) && !dstTag)
423 return tecDST_TAG_NEEDED;
424
425 // If the destination is the claim owner, and this is a claim
426 // transaction, that's the dst account sending funds to itself. It
427 // can bypass deposit auth.
428 bool const canBypassDepositAuth = dst == claimOwner &&
429 depositAuthPolicy == DepositAuthPolicy::dstCanBypass;
430
431 if (!canBypassDepositAuth && (sleDst->getFlags() & lsfDepositAuth) &&
432 !psb.exists(keylet::depositPreauth(dst, src)))
433 {
434 return tecNO_PERMISSION;
435 }
436 }
437 else if (!amt.native() || canCreate == CanCreateDstPolicy::no)
438 {
439 return tecNO_DST;
440 }
441
442 if (amt.native())
443 {
444 auto const sleSrc = psb.peek(keylet::account(src));
445 XRPL_ASSERT(sleSrc, "ripple::transferHelper : non-null source account");
446 if (!sleSrc)
447 return tecINTERNAL;
448
449 {
450 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
451 auto const reserve = psb.fees().accountReserve(ownerCount);
452
453 auto const availableBalance = [&]() -> STAmount {
454 STAmount const curBal = (*sleSrc)[sfBalance];
455 // Checking that account == src and postFeeBalance == curBal is
456 // not strictly nessisary, but helps protect against future
457 // changes
458 if (!submittingAccountInfo ||
459 submittingAccountInfo->account != src ||
460 submittingAccountInfo->postFeeBalance != curBal)
461 return curBal;
462 return submittingAccountInfo->preFeeBalance;
463 }();
464
465 if (availableBalance < amt + reserve)
466 {
467 return tecUNFUNDED_PAYMENT;
468 }
469 }
470
471 auto sleDst = psb.peek(dstK);
472 if (!sleDst)
473 {
474 if (canCreate == CanCreateDstPolicy::no)
475 {
476 // Already checked, but OK to check again
477 return tecNO_DST;
478 }
479 if (amt < psb.fees().accountReserve(0))
480 {
481 JLOG(j.trace()) << "Insufficient payment to create account.";
482 return tecNO_DST_INSUF_XRP;
483 }
484
485 // Create the account.
486 std::uint32_t const seqno{
487 psb.rules().enabled(featureDeletableAccounts) ? psb.seq() : 1};
488
489 sleDst = std::make_shared<SLE>(dstK);
490 sleDst->setAccountID(sfAccount, dst);
491 sleDst->setFieldU32(sfSequence, seqno);
492
493 psb.insert(sleDst);
494 }
495
496 (*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
497 (*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
498 psb.update(sleSrc);
499 psb.update(sleDst);
500
501 return tesSUCCESS;
502 }
503
504 auto const result = flow(
505 psb,
506 amt,
507 src,
508 dst,
509 STPathSet{},
510 /*default path*/ true,
511 /*partial payment*/ false,
512 /*owner pays transfer fee*/ true,
513 /*offer crossing*/ OfferCrossing::no,
514 /*limit quality*/ std::nullopt,
515 /*sendmax*/ std::nullopt,
516 /*domain id*/ std::nullopt,
517 j);
518
519 if (auto const r = result.result();
520 isTesSuccess(r) || isTecClaim(r) || isTerRetry(r))
521 return r;
523}
524
530enum class OnTransferFail {
532 removeClaim,
534 keepClaim
535};
536
537struct FinalizeClaimHelperResult
538{
540 std::optional<TER> mainFundsTer;
541 // TER for transfering the reward funds
542 std::optional<TER> rewardTer;
543 // TER for removing the sle (if is sle is to be removed)
544 std::optional<TER> rmSleTer;
545
546 // Helper to check for overall success. If there wasn't overall success the
547 // individual ters can be used to decide what needs to be done.
548 bool
549 isTesSuccess() const
550 {
551 return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS &&
552 (!rmSleTer || *rmSleTer == tesSUCCESS);
553 }
554
555 TER
556 ter() const
557 {
558 if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) &&
559 (!rewardTer || *rewardTer == tesSUCCESS) &&
560 (!rmSleTer || *rmSleTer == tesSUCCESS))
561 return tesSUCCESS;
562
563 // if any phase return a tecINTERNAL or a tef, prefer returning those
564 // codes
565 if (mainFundsTer &&
566 (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
567 return *mainFundsTer;
568 if (rewardTer &&
569 (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
570 return *rewardTer;
571 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
572 return *rmSleTer;
573
574 // Only after the tecINTERNAL and tef are checked, return the first
575 // non-success error code.
576 if (mainFundsTer && mainFundsTer != tesSUCCESS)
577 return *mainFundsTer;
578 if (rewardTer && rewardTer != tesSUCCESS)
579 return *rewardTer;
580 if (rmSleTer && rmSleTer != tesSUCCESS)
581 return *rmSleTer;
582 return tesSUCCESS;
583 }
584};
585
614FinalizeClaimHelperResult
615finalizeClaimHelper(
616 PaymentSandbox& outerSb,
617 STXChainBridge const& bridgeSpec,
618 AccountID const& dst,
619 std::optional<std::uint32_t> const& dstTag,
620 AccountID const& claimOwner,
621 STAmount const& sendingAmount,
622 AccountID const& rewardPoolSrc,
623 STAmount const& rewardPool,
624 std::vector<AccountID> const& rewardAccounts,
625 STXChainBridge::ChainType const srcChain,
626 Keylet const& claimIDKeylet,
627 OnTransferFail onTransferFail,
628 DepositAuthPolicy depositAuthPolicy,
630{
631 FinalizeClaimHelperResult result;
632
633 STXChainBridge::ChainType const dstChain =
635 STAmount const thisChainAmount = [&] {
636 STAmount r = sendingAmount;
637 r.setIssue(bridgeSpec.issue(dstChain));
638 return r;
639 }();
640 auto const& thisDoor = bridgeSpec.door(dstChain);
641
642 {
643 PaymentSandbox innerSb{&outerSb};
644 // If distributing the reward pool fails, the mainFunds transfer should
645 // be rolled back
646 //
647 // If the claimid is removed, the rewards should be distributed
648 // even if the mainFunds fails.
649 //
650 // If OnTransferFail::removeClaim, the claim should be removed even if
651 // the rewards cannot be distributed.
652
653 // transfer funds to the dst
654 result.mainFundsTer = transferHelper(
655 innerSb,
656 thisDoor,
657 dst,
658 dstTag,
659 claimOwner,
660 thisChainAmount,
661 CanCreateDstPolicy::yes,
662 depositAuthPolicy,
664 j);
665
666 if (!isTesSuccess(*result.mainFundsTer) &&
667 onTransferFail == OnTransferFail::keepClaim)
668 {
669 return result;
670 }
671
672 // handle the reward pool
673 result.rewardTer = [&]() -> TER {
674 if (rewardAccounts.empty())
675 return tesSUCCESS;
676
677 // distribute the reward pool
678 // if the transfer failed, distribute the pool for "OnTransferFail"
679 // cases (the attesters did their job)
680 STAmount const share = [&] {
681 auto const round_mode =
682 innerSb.rules().enabled(fixXChainRewardRounding)
685 saveNumberRoundMode _{Number::setround(round_mode)};
686
687 STAmount const den{rewardAccounts.size()};
688 return divide(rewardPool, den, rewardPool.issue());
689 }();
690 STAmount distributed = rewardPool.zeroed();
691 for (auto const& rewardAccount : rewardAccounts)
692 {
693 auto const thTer = transferHelper(
694 innerSb,
695 rewardPoolSrc,
696 rewardAccount,
697 /*dstTag*/ std::nullopt,
698 // claim owner is not relevant to distributing rewards
699 /*claimOwner*/ std::nullopt,
700 share,
701 CanCreateDstPolicy::no,
702 DepositAuthPolicy::normal,
704 j);
705
706 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
707 return thTer;
708
709 if (isTesSuccess(thTer))
710 distributed += share;
711
712 // let txn succeed if error distributing rewards (other than
713 // inability to pay)
714 }
715
716 if (distributed > rewardPool)
717 return tecINTERNAL;
718
719 return tesSUCCESS;
720 }();
721
722 if (!isTesSuccess(*result.rewardTer) &&
723 (onTransferFail == OnTransferFail::keepClaim ||
724 *result.rewardTer == tecINTERNAL))
725 {
726 return result;
727 }
728
729 if (!isTesSuccess(*result.mainFundsTer) ||
730 isTesSuccess(*result.rewardTer))
731 {
732 // Note: if the mainFunds transfer succeeds and the result transfer
733 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
734 // rolled back)
735 innerSb.apply(outerSb);
736 }
737 }
738
739 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
740 {
741 auto const cidOwner = (*sleClaimID)[sfAccount];
742 {
743 // Remove the claim id
744 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
745 auto const page = (*sleClaimID)[sfOwnerNode];
746 if (!outerSb.dirRemove(
747 keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
748 {
749 JLOG(j.fatal())
750 << "Unable to delete xchain seq number from owner.";
751 result.rmSleTer = tefBAD_LEDGER;
752 return result;
753 }
754
755 // Remove the claim id from the ledger
756 outerSb.erase(sleClaimID);
757
758 adjustOwnerCount(outerSb, sleOwner, -1, j);
759 }
760 }
761
762 return result;
763}
764
775getSignersListAndQuorum(
776 ReadView const& view,
777 SLE const& sleBridge,
779{
782
783 AccountID const thisDoor = sleBridge[sfAccount];
784 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
785
786 if (!sleDoor)
787 {
788 return {r, q, tecINTERNAL};
789 }
790
791 auto const sleS = view.read(keylet::signers(sleBridge[sfAccount]));
792 if (!sleS)
793 {
794 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
795 }
796 q = (*sleS)[sfSignerQuorum];
797
798 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
799
800 if (!accountSigners)
801 {
802 return {r, q, tecINTERNAL};
803 }
804
805 for (auto const& as : *accountSigners)
806 {
807 r[as.account] = as.weight;
808 }
809
810 return {std::move(r), q, tesSUCCESS};
811};
812
813template <class R, class F>
815readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
816{
817 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
818 if (auto r = getter(bridgeSpec, ct))
819 {
820 if ((*r)[sfXChainBridge] == bridgeSpec)
821 return r;
822 }
823 return nullptr;
824 };
825 if (auto r = tryGet(STXChainBridge::ChainType::locking))
826 return r;
828}
829
831peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
832{
833 return readOrpeekBridge<SLE>(
834 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
835 -> std::shared_ptr<SLE> { return v.peek(keylet::bridge(b, ct)); },
836 bridgeSpec);
837}
838
840readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
841{
842 return readOrpeekBridge<SLE const>(
843 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
845 return v.read(keylet::bridge(b, ct));
846 },
847 bridgeSpec);
848}
849
850// Precondition: all the claims in the range are consistent. They must sign for
851// the same event (amount, sending account, claim id, etc).
852template <class TIter>
853TER
854applyClaimAttestations(
855 ApplyView& view,
856 RawView& rawView,
857 TIter attBegin,
858 TIter attEnd,
859 STXChainBridge const& bridgeSpec,
860 STXChainBridge::ChainType const srcChain,
862 std::uint32_t quorum,
864{
865 if (attBegin == attEnd)
866 return tesSUCCESS;
867
868 PaymentSandbox psb(&view);
869
870 auto const claimIDKeylet =
871 keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
872
873 struct ScopeResult
874 {
875 OnNewAttestationResult newAttResult;
876 STAmount rewardAmount;
877 AccountID cidOwner;
878 };
879
880 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
881 // This lambda is ugly - admittedly. The purpose of this lambda is to
882 // limit the scope of sles so they don't overlap with
883 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
884 // views, it's important that the sle's lifetime doesn't overlap.
885 auto const sleClaimID = psb.peek(claimIDKeylet);
886 if (!sleClaimID)
888
889 // Add claims that are part of the signer's list to the "claims" vector
891 atts.reserve(std::distance(attBegin, attEnd));
892 for (auto att = attBegin; att != attEnd; ++att)
893 {
894 if (!signersList.contains(att->attestationSignerAccount))
895 continue;
896 atts.push_back(*att);
897 }
898
899 if (atts.empty())
900 {
902 }
903
904 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
905 if (attBegin->sendingAccount != otherChainSource)
906 {
908 }
909
910 {
911 STXChainBridge::ChainType const dstChain =
913
914 STXChainBridge::ChainType const attDstChain =
915 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
916
917 if (attDstChain != dstChain)
918 {
920 }
921 }
922
923 XChainClaimAttestations curAtts{
924 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
925
926 auto const newAttResult = onNewAttestations(
927 curAtts,
928 view,
929 &atts[0],
930 &atts[0] + atts.size(),
931 quorum,
932 signersList,
933 j);
934
935 // update the claim id
936 sleClaimID->setFieldArray(
937 sfXChainClaimAttestations, curAtts.toSTArray());
938 psb.update(sleClaimID);
939
940 return ScopeResult{
941 newAttResult,
942 (*sleClaimID)[sfSignatureReward],
943 (*sleClaimID)[sfAccount]};
944 }();
945
946 if (!scopeResult.has_value())
947 return scopeResult.error();
948
949 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
950 auto const& [rewardAccounts, attListChanged] = newAttResult;
951 if (rewardAccounts && attBegin->dst)
952 {
953 auto const r = finalizeClaimHelper(
954 psb,
955 bridgeSpec,
956 *attBegin->dst,
957 /*dstTag*/ std::nullopt,
958 cidOwner,
959 attBegin->sendingAmount,
960 cidOwner,
961 rewardAmount,
962 *rewardAccounts,
963 srcChain,
964 claimIDKeylet,
965 OnTransferFail::keepClaim,
966 DepositAuthPolicy::normal,
967 j);
968
969 auto const rTer = r.ter();
970
971 if (!isTesSuccess(rTer) &&
972 (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
973 return rTer;
974 }
975
976 psb.apply(rawView);
977
978 return tesSUCCESS;
979}
980
981template <class TIter>
982TER
983applyCreateAccountAttestations(
984 ApplyView& view,
985 RawView& rawView,
986 TIter attBegin,
987 TIter attEnd,
988 AccountID const& doorAccount,
989 Keylet const& doorK,
990 STXChainBridge const& bridgeSpec,
991 Keylet const& bridgeK,
992 STXChainBridge::ChainType const srcChain,
994 std::uint32_t quorum,
996{
997 if (attBegin == attEnd)
998 return tesSUCCESS;
999
1000 PaymentSandbox psb(&view);
1001
1002 auto const claimCountResult = [&]() -> Expected<std::uint64_t, TER> {
1003 auto const sleBridge = psb.peek(bridgeK);
1004 if (!sleBridge)
1005 return Unexpected(tecINTERNAL);
1006
1007 return (*sleBridge)[sfXChainAccountClaimCount];
1008 }();
1009
1010 if (!claimCountResult.has_value())
1011 return claimCountResult.error();
1012
1013 std::uint64_t const claimCount = claimCountResult.value();
1014
1015 if (attBegin->createCount <= claimCount)
1016 {
1018 }
1019 if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims)
1020 {
1021 // Limit the number of claims on the account
1023 }
1024
1025 {
1026 STXChainBridge::ChainType const dstChain =
1028
1029 STXChainBridge::ChainType const attDstChain =
1030 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
1031
1032 if (attDstChain != dstChain)
1033 {
1034 return tecXCHAIN_WRONG_CHAIN;
1035 }
1036 }
1037
1038 auto const claimIDKeylet =
1039 keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
1040
1041 struct ScopeResult
1042 {
1043 OnNewAttestationResult newAttResult;
1044 bool createCID;
1045 XChainCreateAccountAttestations curAtts;
1046 };
1047
1048 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1049 // This lambda is ugly - admittedly. The purpose of this lambda is to
1050 // limit the scope of sles so they don't overlap with
1051 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1052 // views, it's important that the sle's lifetime doesn't overlap.
1053
1054 // sleClaimID may be null. If it's null it isn't created until the end
1055 // of this function (if needed)
1056 auto const sleClaimID = psb.peek(claimIDKeylet);
1057 bool createCID = false;
1058 if (!sleClaimID)
1059 {
1060 createCID = true;
1061
1062 auto const sleDoor = psb.peek(doorK);
1063 if (!sleDoor)
1064 return Unexpected(tecINTERNAL);
1065
1066 // Check reserve
1067 auto const balance = (*sleDoor)[sfBalance];
1068 auto const reserve =
1069 psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
1070
1071 if (balance < reserve)
1073 }
1074
1076 atts.reserve(std::distance(attBegin, attEnd));
1077 for (auto att = attBegin; att != attEnd; ++att)
1078 {
1079 if (!signersList.contains(att->attestationSignerAccount))
1080 continue;
1081 atts.push_back(*att);
1082 }
1083 if (atts.empty())
1084 {
1086 }
1087
1088 XChainCreateAccountAttestations curAtts = [&] {
1089 if (sleClaimID)
1090 return XChainCreateAccountAttestations{
1091 sleClaimID->getFieldArray(
1092 sfXChainCreateAccountAttestations)};
1093 return XChainCreateAccountAttestations{};
1094 }();
1095
1096 auto const newAttResult = onNewAttestations(
1097 curAtts,
1098 view,
1099 &atts[0],
1100 &atts[0] + atts.size(),
1101 quorum,
1102 signersList,
1103 j);
1104
1105 if (!createCID)
1106 {
1107 // Modify the object before it's potentially deleted, so the meta
1108 // data will include the new attestations
1109 if (!sleClaimID)
1110 return Unexpected(tecINTERNAL);
1111 sleClaimID->setFieldArray(
1112 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1113 psb.update(sleClaimID);
1114 }
1115 return ScopeResult{newAttResult, createCID, curAtts};
1116 }();
1117
1118 if (!scopeResult.has_value())
1119 return scopeResult.error();
1120
1121 auto const& [attResult, createCID, curAtts] = scopeResult.value();
1122 auto const& [rewardAccounts, attListChanged] = attResult;
1123
1124 // Account create transactions must happen in order
1125 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
1126 {
1127 auto const r = finalizeClaimHelper(
1128 psb,
1129 bridgeSpec,
1130 attBegin->toCreate,
1131 /*dstTag*/ std::nullopt,
1132 doorAccount,
1133 attBegin->sendingAmount,
1134 /*rewardPoolSrc*/ doorAccount,
1135 attBegin->rewardAmount,
1136 *rewardAccounts,
1137 srcChain,
1138 claimIDKeylet,
1139 OnTransferFail::removeClaim,
1140 DepositAuthPolicy::normal,
1141 j);
1142
1143 auto const rTer = r.ter();
1144
1145 if (!isTesSuccess(rTer))
1146 {
1147 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT ||
1148 isTefFailure(rTer))
1149 return rTer;
1150 }
1151 // Move past this claim id even if it fails, so it doesn't block
1152 // subsequent claim ids
1153 auto const sleBridge = psb.peek(bridgeK);
1154 if (!sleBridge)
1155 return tecINTERNAL;
1156 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
1157 psb.update(sleBridge);
1158 }
1159 else if (createCID)
1160 {
1161 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
1162 (*createdSleClaimID)[sfAccount] = doorAccount;
1163 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
1164 (*createdSleClaimID)[sfXChainAccountCreateCount] =
1165 attBegin->createCount;
1166 createdSleClaimID->setFieldArray(
1167 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1168
1169 // Add to owner directory of the door account
1170 auto const page = psb.dirInsert(
1171 keylet::ownerDir(doorAccount),
1172 claimIDKeylet,
1173 describeOwnerDir(doorAccount));
1174 if (!page)
1175 return tecDIR_FULL;
1176 (*createdSleClaimID)[sfOwnerNode] = *page;
1177
1178 auto const sleDoor = psb.peek(doorK);
1179 if (!sleDoor)
1180 return tecINTERNAL;
1181
1182 // Reserve was already checked
1183 adjustOwnerCount(psb, sleDoor, 1, j);
1184 psb.insert(createdSleClaimID);
1185 psb.update(sleDoor);
1186 }
1187
1188 psb.apply(rawView);
1189
1190 return tesSUCCESS;
1191}
1192
1193template <class TAttestation>
1195toClaim(STTx const& tx)
1196{
1197 static_assert(
1200
1201 try
1202 {
1203 STObject o{tx};
1204 o.setAccountID(sfAccount, o[sfOtherChainSource]);
1205 return TAttestation(o);
1206 }
1207 catch (...)
1208 {
1209 }
1210 return std::nullopt;
1211}
1212
1213template <class TAttestation>
1214NotTEC
1215attestationpreflight(PreflightContext const& ctx)
1216{
1217 if (!publicKeyType(ctx.tx[sfPublicKey]))
1218 return temMALFORMED;
1219
1220 auto const att = toClaim<TAttestation>(ctx.tx);
1221 if (!att)
1222 return temMALFORMED;
1223
1224 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1225 if (!att->verify(bridgeSpec))
1226 return temXCHAIN_BAD_PROOF;
1227 if (!att->validAmounts())
1228 return temXCHAIN_BAD_PROOF;
1229
1230 if (att->sendingAmount.signum() <= 0)
1231 return temXCHAIN_BAD_PROOF;
1232 auto const expectedIssue =
1233 bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
1234 if (att->sendingAmount.issue() != expectedIssue)
1235 return temXCHAIN_BAD_PROOF;
1236
1237 return tesSUCCESS;
1238}
1239
1240template <class TAttestation>
1241TER
1242attestationPreclaim(PreclaimContext const& ctx)
1243{
1244 auto const att = toClaim<TAttestation>(ctx.tx);
1245 if (!att)
1246 return tecINTERNAL; // checked in preflight
1247
1248 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1249 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1250 if (!sleBridge)
1251 {
1252 return tecNO_ENTRY;
1253 }
1254
1255 AccountID const attestationSignerAccount{
1256 ctx.tx[sfAttestationSignerAccount]};
1257 PublicKey const pk{ctx.tx[sfPublicKey]};
1258
1259 // signersList is a map from account id to weights
1260 auto const [signersList, quorum, slTer] =
1261 getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
1262
1263 if (!isTesSuccess(slTer))
1264 return slTer;
1265
1266 return checkAttestationPublicKey(
1267 ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
1268}
1269
1270template <class TAttestation>
1271TER
1272attestationDoApply(ApplyContext& ctx)
1273{
1274 auto const att = toClaim<TAttestation>(ctx.tx);
1275 if (!att)
1276 // Should already be checked in preflight
1277 return tecINTERNAL;
1278
1279 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1280
1281 struct ScopeResult
1282 {
1283 STXChainBridge::ChainType srcChain;
1285 std::uint32_t quorum;
1286 AccountID thisDoor;
1287 Keylet bridgeK;
1288 };
1289
1290 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1291 // This lambda is ugly - admittedly. The purpose of this lambda is to
1292 // limit the scope of sles so they don't overlap with
1293 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1294 // views, it's important that the sle's lifetime doesn't overlap.
1295 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
1296 if (!sleBridge)
1297 {
1298 return Unexpected(tecNO_ENTRY);
1299 }
1300 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
1301 AccountID const thisDoor = (*sleBridge)[sfAccount];
1302
1304 {
1305 if (thisDoor == bridgeSpec.lockingChainDoor())
1307 else if (thisDoor == bridgeSpec.issuingChainDoor())
1309 else
1310 return Unexpected(tecINTERNAL);
1311 }
1312 STXChainBridge::ChainType const srcChain =
1314
1315 // signersList is a map from account id to weights
1316 auto [signersList, quorum, slTer] =
1317 getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
1318
1319 if (!isTesSuccess(slTer))
1320 return Unexpected(slTer);
1321
1322 return ScopeResult{
1323 srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
1324 }();
1325
1326 if (!scopeResult.has_value())
1327 return scopeResult.error();
1328
1329 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] =
1330 scopeResult.value();
1331
1332 static_assert(
1335
1337 {
1338 return applyClaimAttestations(
1339 ctx.view(),
1340 ctx.rawView(),
1341 &*att,
1342 &*att + 1,
1343 bridgeSpec,
1344 srcChain,
1345 signersList,
1346 quorum,
1347 ctx.journal);
1348 }
1349 else if constexpr (std::is_same_v<
1350 TAttestation,
1351 Attestations::AttestationCreateAccount>)
1352 {
1353 return applyCreateAccountAttestations(
1354 ctx.view(),
1355 ctx.rawView(),
1356 &*att,
1357 &*att + 1,
1358 thisDoor,
1359 keylet::account(thisDoor),
1360 bridgeSpec,
1361 bridgeK,
1362 srcChain,
1363 signersList,
1364 quorum,
1365 ctx.journal);
1366 }
1367}
1368
1369} // namespace
1370//------------------------------------------------------------------------------
1371
1372NotTEC
1374{
1375 auto const account = ctx.tx[sfAccount];
1376 auto const reward = ctx.tx[sfSignatureReward];
1377 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1378 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1379 // Doors must be distinct to help prevent transaction replay attacks
1380 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
1381 {
1383 }
1384
1385 if (bridgeSpec.lockingChainDoor() != account &&
1386 bridgeSpec.issuingChainDoor() != account)
1387 {
1389 }
1390
1391 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1392 isXRP(bridgeSpec.issuingChainIssue()))
1393 {
1394 // Because ious and xrp have different numeric ranges, both the src and
1395 // dst issues must be both XRP or both IOU.
1397 }
1398
1399 if (!isXRP(reward) || reward.signum() < 0)
1400 {
1402 }
1403
1404 if (minAccountCreate &&
1405 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1406 !isXRP(bridgeSpec.lockingChainIssue()) ||
1407 !isXRP(bridgeSpec.issuingChainIssue())))
1408 {
1410 }
1411
1412 if (isXRP(bridgeSpec.issuingChainIssue()))
1413 {
1414 // Issuing account must be the root account for XRP (which presumably
1415 // owns all the XRP). This is done so the issuing account can't "run
1416 // out" of wrapped tokens.
1417 static auto const rootAccount = calcAccountID(
1419 KeyType::secp256k1, generateSeed("masterpassphrase"))
1420 .first);
1421 if (bridgeSpec.issuingChainDoor() != rootAccount)
1422 {
1424 }
1425 }
1426 else
1427 {
1428 // Issuing account must be the issuer for non-XRP. This is done so the
1429 // issuing account can't "run out" of wrapped tokens.
1430 if (bridgeSpec.issuingChainDoor() !=
1431 bridgeSpec.issuingChainIssue().account)
1432 {
1434 }
1435 }
1436
1437 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
1438 {
1439 // If the locking chain door is locking their own asset, in some sense
1440 // nothing is being locked. Disallow this.
1442 }
1443
1444 return tesSUCCESS;
1445}
1446
1447TER
1449{
1450 auto const account = ctx.tx[sfAccount];
1451 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1452 STXChainBridge::ChainType const chainType =
1453 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1454
1455 {
1456 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
1457 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
1458 };
1459
1460 if (hasBridge(STXChainBridge::ChainType::issuing) ||
1462 {
1463 return tecDUPLICATE;
1464 }
1465 }
1466
1467 if (!isXRP(bridgeSpec.issue(chainType)))
1468 {
1469 auto const sleIssuer =
1470 ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
1471
1472 if (!sleIssuer)
1473 return tecNO_ISSUER;
1474
1475 // Allowing clawing back funds would break the bridge's invariant that
1476 // wrapped funds are always backed by locked funds
1477 if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
1478 return tecNO_PERMISSION;
1479 }
1480
1481 {
1482 // Check reserve
1483 auto const sleAcc = ctx.view.read(keylet::account(account));
1484 if (!sleAcc)
1485 return terNO_ACCOUNT;
1486
1487 auto const balance = (*sleAcc)[sfBalance];
1488 auto const reserve =
1489 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1490
1491 if (balance < reserve)
1493 }
1494
1495 return tesSUCCESS;
1496}
1497
1498TER
1500{
1501 auto const account = ctx_.tx[sfAccount];
1502 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1503 auto const reward = ctx_.tx[sfSignatureReward];
1504 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1505
1506 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1507 if (!sleAcct)
1508 return tecINTERNAL;
1509
1510 STXChainBridge::ChainType const chainType =
1511 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1512
1513 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
1514 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1515
1516 (*sleBridge)[sfAccount] = account;
1517 (*sleBridge)[sfSignatureReward] = reward;
1518 if (minAccountCreate)
1519 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1520 (*sleBridge)[sfXChainBridge] = bridgeSpec;
1521 (*sleBridge)[sfXChainClaimID] = 0;
1522 (*sleBridge)[sfXChainAccountCreateCount] = 0;
1523 (*sleBridge)[sfXChainAccountClaimCount] = 0;
1524
1525 // Add to owner directory
1526 {
1527 auto const page = ctx_.view().dirInsert(
1528 keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
1529 if (!page)
1530 return tecDIR_FULL;
1531 (*sleBridge)[sfOwnerNode] = *page;
1532 }
1533
1534 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1535
1536 ctx_.view().insert(sleBridge);
1537 ctx_.view().update(sleAcct);
1538
1539 return tesSUCCESS;
1540}
1541
1542//------------------------------------------------------------------------------
1543
1549
1550NotTEC
1552{
1553 auto const account = ctx.tx[sfAccount];
1554 auto const reward = ctx.tx[~sfSignatureReward];
1555 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1556 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1557 bool const clearAccountCreate =
1559
1560 if (!reward && !minAccountCreate && !clearAccountCreate)
1561 {
1562 // Must change something
1563 return temMALFORMED;
1564 }
1565
1566 if (minAccountCreate && clearAccountCreate)
1567 {
1568 // Can't both clear and set account create in the same txn
1569 return temMALFORMED;
1570 }
1571
1572 if (bridgeSpec.lockingChainDoor() != account &&
1573 bridgeSpec.issuingChainDoor() != account)
1574 {
1576 }
1577
1578 if (reward && (!isXRP(*reward) || reward->signum() < 0))
1579 {
1581 }
1582
1583 if (minAccountCreate &&
1584 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1585 !isXRP(bridgeSpec.lockingChainIssue()) ||
1586 !isXRP(bridgeSpec.issuingChainIssue())))
1587 {
1589 }
1590
1591 return tesSUCCESS;
1592}
1593
1594TER
1596{
1597 auto const account = ctx.tx[sfAccount];
1598 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1599
1600 STXChainBridge::ChainType const chainType =
1601 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1602
1603 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1604 {
1605 return tecNO_ENTRY;
1606 }
1607
1608 return tesSUCCESS;
1609}
1610
1611TER
1613{
1614 auto const account = ctx_.tx[sfAccount];
1615 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1616 auto const reward = ctx_.tx[~sfSignatureReward];
1617 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1618 bool const clearAccountCreate =
1620
1621 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1622 if (!sleAcct)
1623 return tecINTERNAL;
1624
1625 STXChainBridge::ChainType const chainType =
1626 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1627
1628 auto const sleBridge =
1629 ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
1630 if (!sleBridge)
1631 return tecINTERNAL;
1632
1633 if (reward)
1634 (*sleBridge)[sfSignatureReward] = *reward;
1635 if (minAccountCreate)
1636 {
1637 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1638 }
1639 if (clearAccountCreate &&
1640 sleBridge->isFieldPresent(sfMinAccountCreateAmount))
1641 {
1642 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
1643 }
1644 ctx_.view().update(sleBridge);
1645
1646 return tesSUCCESS;
1647}
1648
1649//------------------------------------------------------------------------------
1650
1651NotTEC
1653{
1654 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1655 auto const amount = ctx.tx[sfAmount];
1656
1657 if (amount.signum() <= 0 ||
1658 (amount.issue() != bridgeSpec.lockingChainIssue() &&
1659 amount.issue() != bridgeSpec.issuingChainIssue()))
1660 {
1661 return temBAD_AMOUNT;
1662 }
1663
1664 return tesSUCCESS;
1665}
1666
1667TER
1669{
1670 AccountID const account = ctx.tx[sfAccount];
1671 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1672 STAmount const& thisChainAmount = ctx.tx[sfAmount];
1673 auto const claimID = ctx.tx[sfXChainClaimID];
1674
1675 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1676 if (!sleBridge)
1677 {
1678 return tecNO_ENTRY;
1679 }
1680
1681 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
1682 {
1683 return tecNO_DST;
1684 }
1685
1686 auto const thisDoor = (*sleBridge)[sfAccount];
1687 bool isLockingChain = false;
1688 {
1689 if (thisDoor == bridgeSpec.lockingChainDoor())
1690 isLockingChain = true;
1691 else if (thisDoor == bridgeSpec.issuingChainDoor())
1692 isLockingChain = false;
1693 else
1694 return tecINTERNAL;
1695 }
1696
1697 {
1698 // Check that the amount specified matches the expected issue
1699
1700 if (isLockingChain)
1701 {
1702 if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
1704 }
1705 else
1706 {
1707 if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
1709 }
1710 }
1711
1712 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1713 isXRP(bridgeSpec.issuingChainIssue()))
1714 {
1715 // Should have been caught when creating the bridge
1716 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
1717 // and the numeric issues that need to be addressed with that.
1718 return tecINTERNAL;
1719 }
1720
1721 auto const otherChainAmount = [&]() -> STAmount {
1722 STAmount r(thisChainAmount);
1723 if (isLockingChain)
1724 r.setIssue(bridgeSpec.issuingChainIssue());
1725 else
1726 r.setIssue(bridgeSpec.lockingChainIssue());
1727 return r;
1728 }();
1729
1730 auto const sleClaimID =
1731 ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
1732 {
1733 // Check that the sequence number is owned by the sender of this
1734 // transaction
1735 if (!sleClaimID)
1736 {
1737 return tecXCHAIN_NO_CLAIM_ID;
1738 }
1739
1740 if ((*sleClaimID)[sfAccount] != account)
1741 {
1742 // Sequence number isn't owned by the sender of this transaction
1744 }
1745 }
1746
1747 // quorum is checked in `doApply`
1748 return tesSUCCESS;
1749}
1750
1751TER
1753{
1754 PaymentSandbox psb(&ctx_.view());
1755
1756 AccountID const account = ctx_.tx[sfAccount];
1757 auto const dst = ctx_.tx[sfDestination];
1758 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
1759 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
1760 auto const claimID = ctx_.tx[sfXChainClaimID];
1761 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1762
1763 struct ScopeResult
1764 {
1765 std::vector<AccountID> rewardAccounts;
1766 AccountID rewardPoolSrc;
1767 STAmount sendingAmount;
1769 STAmount signatureReward;
1770 };
1771
1772 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1773 // This lambda is ugly - admittedly. The purpose of this lambda is to
1774 // limit the scope of sles so they don't overlap with
1775 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1776 // views, it's important that the sle's lifetime doesn't overlap.
1777
1778 auto const sleAcct = psb.peek(keylet::account(account));
1779 auto const sleBridge = peekBridge(psb, bridgeSpec);
1780 auto const sleClaimID = psb.peek(claimIDKeylet);
1781
1782 if (!(sleBridge && sleClaimID && sleAcct))
1783 return Unexpected(tecINTERNAL);
1784
1785 AccountID const thisDoor = (*sleBridge)[sfAccount];
1786
1788 {
1789 if (thisDoor == bridgeSpec.lockingChainDoor())
1791 else if (thisDoor == bridgeSpec.issuingChainDoor())
1793 else
1794 return Unexpected(tecINTERNAL);
1795 }
1796 STXChainBridge::ChainType const srcChain =
1798
1799 auto const sendingAmount = [&]() -> STAmount {
1800 STAmount r(thisChainAmount);
1801 r.setIssue(bridgeSpec.issue(srcChain));
1802 return r;
1803 }();
1804
1805 auto const [signersList, quorum, slTer] =
1806 getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1807
1808 if (!isTesSuccess(slTer))
1809 return Unexpected(slTer);
1810
1812 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1813
1814 auto const claimR = onClaim(
1815 curAtts,
1816 psb,
1817 sendingAmount,
1818 /*wasLockingChainSend*/ srcChain ==
1820 quorum,
1821 signersList,
1822 ctx_.journal);
1823 if (!claimR.has_value())
1824 return Unexpected(claimR.error());
1825
1826 return ScopeResult{
1827 claimR.value(),
1828 (*sleClaimID)[sfAccount],
1829 sendingAmount,
1830 srcChain,
1831 (*sleClaimID)[sfSignatureReward],
1832 };
1833 }();
1834
1835 if (!scopeResult.has_value())
1836 return scopeResult.error();
1837
1838 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
1839 scopeResult.value();
1840 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
1841
1842 auto const r = finalizeClaimHelper(
1843 psb,
1844 bridgeSpec,
1845 dst,
1846 dstTag,
1847 /*claimOwner*/ account,
1848 sendingAmount,
1849 rewardPoolSrc,
1850 signatureReward,
1851 rewardAccounts,
1852 srcChain,
1853 claimIDKeylet,
1854 OnTransferFail::keepClaim,
1855 DepositAuthPolicy::dstCanBypass,
1856 ctx_.journal);
1857 if (!r.isTesSuccess())
1858 return r.ter();
1859
1860 psb.apply(ctx_.rawView());
1861
1862 return tesSUCCESS;
1863}
1864
1865//------------------------------------------------------------------------------
1866
1869{
1870 auto const maxSpend = [&] {
1871 auto const amount = ctx.tx[sfAmount];
1872 if (amount.native() && amount.signum() > 0)
1873 return amount.xrp();
1874 return XRPAmount{beast::zero};
1875 }();
1876
1877 return TxConsequences{ctx.tx, maxSpend};
1878}
1879
1880NotTEC
1882{
1883 auto const amount = ctx.tx[sfAmount];
1884 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1885
1886 if (amount.signum() <= 0 || !isLegalNet(amount))
1887 return temBAD_AMOUNT;
1888
1889 if (amount.issue() != bridgeSpec.lockingChainIssue() &&
1890 amount.issue() != bridgeSpec.issuingChainIssue())
1891 return temBAD_ISSUER;
1892
1893 return tesSUCCESS;
1894}
1895
1896TER
1898{
1899 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1900 auto const amount = ctx.tx[sfAmount];
1901
1902 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1903 if (!sleBridge)
1904 {
1905 return tecNO_ENTRY;
1906 }
1907
1908 AccountID const thisDoor = (*sleBridge)[sfAccount];
1909 AccountID const account = ctx.tx[sfAccount];
1910
1911 if (thisDoor == account)
1912 {
1913 // Door account can't lock funds onto itself
1914 return tecXCHAIN_SELF_COMMIT;
1915 }
1916
1917 bool isLockingChain = false;
1918 {
1919 if (thisDoor == bridgeSpec.lockingChainDoor())
1920 isLockingChain = true;
1921 else if (thisDoor == bridgeSpec.issuingChainDoor())
1922 isLockingChain = false;
1923 else
1924 return tecINTERNAL;
1925 }
1926
1927 if (isLockingChain)
1928 {
1929 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
1931 }
1932 else
1933 {
1934 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
1936 }
1937
1938 return tesSUCCESS;
1939}
1940
1941TER
1943{
1944 PaymentSandbox psb(&ctx_.view());
1945
1946 auto const account = ctx_.tx[sfAccount];
1947 auto const amount = ctx_.tx[sfAmount];
1948 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1949
1950 if (!psb.read(keylet::account(account)))
1951 return tecINTERNAL;
1952
1953 auto const sleBridge = readBridge(psb, bridgeSpec);
1954 if (!sleBridge)
1955 return tecINTERNAL;
1956
1957 auto const dst = (*sleBridge)[sfAccount];
1958
1959 // Support dipping into reserves to pay the fee
1960 TransferHelperSubmittingAccountInfo submittingAccountInfo{
1962
1963 auto const thTer = transferHelper(
1964 psb,
1965 account,
1966 dst,
1967 /*dstTag*/ std::nullopt,
1968 /*claimOwner*/ std::nullopt,
1969 amount,
1970 CanCreateDstPolicy::no,
1971 DepositAuthPolicy::normal,
1972 submittingAccountInfo,
1973 ctx_.journal);
1974
1975 if (!isTesSuccess(thTer))
1976 return thTer;
1977
1978 psb.apply(ctx_.rawView());
1979
1980 return tesSUCCESS;
1981}
1982
1983//------------------------------------------------------------------------------
1984
1985NotTEC
1987{
1988 auto const reward = ctx.tx[sfSignatureReward];
1989
1990 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
1992
1993 return tesSUCCESS;
1994}
1995
1996TER
1998{
1999 auto const account = ctx.tx[sfAccount];
2000 auto const bridgeSpec = ctx.tx[sfXChainBridge];
2001 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2002
2003 if (!sleBridge)
2004 {
2005 return tecNO_ENTRY;
2006 }
2007
2008 // Check that the reward matches
2009 auto const reward = ctx.tx[sfSignatureReward];
2010
2011 if (reward != (*sleBridge)[sfSignatureReward])
2012 {
2014 }
2015
2016 {
2017 // Check reserve
2018 auto const sleAcc = ctx.view.read(keylet::account(account));
2019 if (!sleAcc)
2020 return terNO_ACCOUNT;
2021
2022 auto const balance = (*sleAcc)[sfBalance];
2023 auto const reserve =
2024 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
2025
2026 if (balance < reserve)
2028 }
2029
2030 return tesSUCCESS;
2031}
2032
2033TER
2035{
2036 auto const account = ctx_.tx[sfAccount];
2037 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
2038 auto const reward = ctx_.tx[sfSignatureReward];
2039 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
2040
2041 auto const sleAcct = ctx_.view().peek(keylet::account(account));
2042 if (!sleAcct)
2043 return tecINTERNAL;
2044
2045 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
2046 if (!sleBridge)
2047 return tecINTERNAL;
2048
2049 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
2050 if (claimID == 0)
2051 return tecINTERNAL; // overflow
2052
2053 (*sleBridge)[sfXChainClaimID] = claimID;
2054
2055 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
2056 if (ctx_.view().exists(claimIDKeylet))
2057 return tecINTERNAL; // already checked out!?!
2058
2059 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
2060
2061 (*sleClaimID)[sfAccount] = account;
2062 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
2063 (*sleClaimID)[sfXChainClaimID] = claimID;
2064 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
2065 (*sleClaimID)[sfSignatureReward] = reward;
2066 sleClaimID->setFieldArray(
2067 sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
2068
2069 // Add to owner directory
2070 {
2071 auto const page = ctx_.view().dirInsert(
2072 keylet::ownerDir(account),
2073 claimIDKeylet,
2074 describeOwnerDir(account));
2075 if (!page)
2076 return tecDIR_FULL;
2077 (*sleClaimID)[sfOwnerNode] = *page;
2078 }
2079
2080 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
2081
2082 ctx_.view().insert(sleClaimID);
2083 ctx_.view().update(sleBridge);
2084 ctx_.view().update(sleAcct);
2085
2086 return tesSUCCESS;
2087}
2088
2089//------------------------------------------------------------------------------
2090
2091NotTEC
2093{
2094 return attestationpreflight<Attestations::AttestationClaim>(ctx);
2095}
2096
2097TER
2099{
2100 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
2101}
2102
2103TER
2105{
2106 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
2107}
2108
2109//------------------------------------------------------------------------------
2110
2111NotTEC
2113{
2114 return attestationpreflight<Attestations::AttestationCreateAccount>(ctx);
2115}
2116
2117TER
2119{
2120 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
2121}
2122
2123TER
2125{
2126 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
2127}
2128
2129//------------------------------------------------------------------------------
2130
2131NotTEC
2133{
2134 auto const amount = ctx.tx[sfAmount];
2135
2136 if (amount.signum() <= 0 || !amount.native())
2137 return temBAD_AMOUNT;
2138
2139 auto const reward = ctx.tx[sfSignatureReward];
2140 if (reward.signum() < 0 || !reward.native())
2141 return temBAD_AMOUNT;
2142
2143 if (reward.issue() != amount.issue())
2144 return temBAD_AMOUNT;
2145
2146 return tesSUCCESS;
2147}
2148
2149TER
2151{
2152 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
2153 STAmount const amount = ctx.tx[sfAmount];
2154 STAmount const reward = ctx.tx[sfSignatureReward];
2155
2156 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2157 if (!sleBridge)
2158 {
2159 return tecNO_ENTRY;
2160 }
2161
2162 if (reward != (*sleBridge)[sfSignatureReward])
2163 {
2165 }
2166
2167 std::optional<STAmount> const minCreateAmount =
2168 (*sleBridge)[~sfMinAccountCreateAmount];
2169
2170 if (!minCreateAmount)
2172
2173 if (amount < *minCreateAmount)
2175
2176 if (minCreateAmount->issue() != amount.issue())
2178
2179 AccountID const thisDoor = (*sleBridge)[sfAccount];
2180 AccountID const account = ctx.tx[sfAccount];
2181 if (thisDoor == account)
2182 {
2183 // Door account can't lock funds onto itself
2184 return tecXCHAIN_SELF_COMMIT;
2185 }
2186
2188 {
2189 if (thisDoor == bridgeSpec.lockingChainDoor())
2191 else if (thisDoor == bridgeSpec.issuingChainDoor())
2193 else
2194 return tecINTERNAL;
2195 }
2196 STXChainBridge::ChainType const dstChain =
2198
2199 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
2201
2202 if (!isXRP(bridgeSpec.issue(dstChain)))
2204
2205 return tesSUCCESS;
2206}
2207
2208TER
2210{
2211 PaymentSandbox psb(&ctx_.view());
2212
2213 AccountID const account = ctx_.tx[sfAccount];
2214 STAmount const amount = ctx_.tx[sfAmount];
2215 STAmount const reward = ctx_.tx[sfSignatureReward];
2216 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
2217
2218 auto const sle = psb.peek(keylet::account(account));
2219 if (!sle)
2220 return tecINTERNAL;
2221
2222 auto const sleBridge = peekBridge(psb, bridge);
2223 if (!sleBridge)
2224 return tecINTERNAL;
2225
2226 auto const dst = (*sleBridge)[sfAccount];
2227
2228 // Support dipping into reserves to pay the fee
2229 TransferHelperSubmittingAccountInfo submittingAccountInfo{
2231 STAmount const toTransfer = amount + reward;
2232 auto const thTer = transferHelper(
2233 psb,
2234 account,
2235 dst,
2236 /*dstTag*/ std::nullopt,
2237 /*claimOwner*/ std::nullopt,
2238 toTransfer,
2239 CanCreateDstPolicy::yes,
2240 DepositAuthPolicy::normal,
2241 submittingAccountInfo,
2242 ctx_.journal);
2243
2244 if (!isTesSuccess(thTer))
2245 return thTer;
2246
2247 (*sleBridge)[sfXChainAccountCreateCount] =
2248 (*sleBridge)[sfXChainAccountCreateCount] + 1;
2249 psb.update(sleBridge);
2250
2251 psb.apply(ctx_.rawView());
2252
2253 return tesSUCCESS;
2254}
2255
2256} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
Stream fatal() const
Definition Journal.h:352
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
ApplyView & view()
beast::Journal const journal
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:319
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static rounding_mode getround()
Definition Number.cpp:47
static rounding_mode setround(rounding_mode mode)
Definition Number.cpp:53
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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.
void setIssue(Asset const &asset)
Set the Issue for this amount.
Definition STAmount.cpp:471
Issue const & issue() const
Definition STAmount.h:496
std::uint32_t getFlags() const
Definition STObject.cpp:537
static ChainType dstChain(bool wasLockingChainSend)
static ChainType srcChain(bool wasLockingChainSend)
AccountID const & issuingChainDoor() const
Issue const & issuingChainIssue() const
Issue const & lockingChainIssue() const
static ChainType otherChain(ChainType ct)
AccountID const & lockingChainDoor() const
Issue const & issue(ChainType ct) const
static Expected< std::vector< SignerEntry >, NotTEC > deserialize(STObject const &obj, beast::Journal journal, std::string_view annotation)
AccountID const account_
Definition Transactor.h:147
XRPAmount mPriorBalance
Definition Transactor.h:148
XRPAmount mSourceBalance
Definition Transactor.h:149
ApplyContext & ctx_
Definition Transactor.h:143
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:58
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
TER doApply() override
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T contains(T... args)
T distance(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T is_same_v
T max(T... args)
void check(bool condition, std::string const &message)
Keylet xChainClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:486
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:380
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition Indexes.cpp:473
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:330
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:500
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:342
std::uint32_t ownerCount(Env const &env, Account const &account)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:48
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:93
constexpr std::uint32_t tfBridgeModifyMask
Definition TxFlags.h:267
bool isXRP(AccountID const &c)
Definition AccountID.h:90
constexpr size_t xbridgeMaxAccountCreateClaims
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
bool isLegalNet(STAmount const &value)
Definition STAmount.h:600
@ lsfRequireDestTag
@ lsfAllowTrustLineClawback
@ lsfDisableMaster
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:1032
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1050
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:105
AccountID calcAccountID(PublicKey const &pk)
@ tefBAD_LEDGER
Definition TER.h:170
bool isTefFailure(TER x) noexcept
Definition TER.h:662
@ no
Definition Steps.h:45
@ yes
Definition Steps.h:45
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
constexpr std::uint32_t tfClearAccountCreateAmount
Definition TxFlags.h:266
@ tecNO_ENTRY
Definition TER.h:306
@ tecNO_DST
Definition TER.h:290
@ tecXCHAIN_SELF_COMMIT
Definition TER.h:350
@ tecXCHAIN_WRONG_CHAIN
Definition TER.h:342
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition TER.h:346
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition TER.h:352
@ tecNO_ISSUER
Definition TER.h:299
@ tecXCHAIN_NO_CLAIM_ID
Definition TER.h:337
@ tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE
Definition TER.h:341
@ tecDIR_FULL
Definition TER.h:287
@ tecXCHAIN_PAYMENT_FAILED
Definition TER.h:349
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition TER.h:339
@ tecXCHAIN_BAD_CLAIM_ID
Definition TER.h:338
@ tecXCHAIN_PROOF_UNKNOWN_KEY
Definition TER.h:340
@ tecDUPLICATE
Definition TER.h:315
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition TER.h:347
@ tecINTERNAL
Definition TER.h:310
@ tecXCHAIN_BAD_TRANSFER_ISSUE
Definition TER.h:336
@ tecNO_PERMISSION
Definition TER.h:305
@ tecDST_TAG_NEEDED
Definition TER.h:309
@ tecXCHAIN_REWARD_MISMATCH
Definition TER.h:343
@ tecXCHAIN_SENDING_ACCOUNT_MISMATCH
Definition TER.h:345
@ tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR
Definition TER.h:351
@ tecUNFUNDED_PAYMENT
Definition TER.h:285
@ tecXCHAIN_NO_SIGNERS_LIST
Definition TER.h:344
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecXCHAIN_ACCOUNT_CREATE_TOO_MANY
Definition TER.h:348
@ tecNO_DST_INSUF_XRP
Definition TER.h:291
bool isTerRetry(TER x) noexcept
Definition TER.h:668
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
STLedgerEntry SLE
Unexpected(E(&)[N]) -> Unexpected< E const * >
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:76
@ terNO_ACCOUNT
Definition TER.h:217
TERSubset< CanCvtToTER > TER
Definition TER.h:645
bool isTecClaim(TER x) noexcept
Definition TER.h:681
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ temBAD_ISSUER
Definition TER.h:93
@ temBAD_AMOUNT
Definition TER.h:89
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition TER.h:135
@ temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
Definition TER.h:136
@ temXCHAIN_EQUAL_DOOR_ACCOUNTS
Definition TER.h:131
@ temXCHAIN_BRIDGE_NONDOOR_OWNER
Definition TER.h:134
@ temMALFORMED
Definition TER.h:87
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition TER.h:133
@ temXCHAIN_BAD_PROOF
Definition TER.h:132
T push_back(T... args)
T reserve(T... args)
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:39
uint256 key
Definition Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
State information when preflighting a tx.
Definition Transactor.h:35