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