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