rippled
Loading...
Searching...
No Matches
XChain_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2022 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpl/beast/unit_test/suite.h>
21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/Indexes.h>
23#include <xrpl/protocol/Issue.h>
24#include <xrpl/protocol/SField.h>
25#include <xrpl/protocol/STXChainBridge.h>
26#include <xrpl/protocol/Serializer.h>
27#include <xrpl/protocol/TER.h>
28#include <xrpl/protocol/XChainAttestations.h>
29
30#include <test/jtx.h>
31#include <test/jtx/Env.h>
32#include <test/jtx/attester.h>
33#include <test/jtx/multisign.h>
34#include <test/jtx/xchain_bridge.h>
35
36#include <functional>
37#include <limits>
38#include <optional>
39#include <random>
40#include <string>
41#include <tuple>
42#include <variant>
43#include <vector>
44
45#include <fstream>
46#include <iostream>
47
48namespace ripple::test {
49
50// SEnv class - encapsulate jtx::Env to make it more user-friendly,
51// for example having APIs that return a *this reference so that calls can be
52// chained (fluent interface) allowing to create an environment and use it
53// without encapsulating it in a curly brace block.
54// ---------------------------------------------------------------------------
55template <class T>
56struct SEnv
57{
59
61 T& s,
63 FeatureBitset features,
64 std::unique_ptr<Logs> logs = nullptr,
66 : env_(s, std::move(config), features, std::move(logs), thresh)
67 {
68 }
69
70 SEnv&
72 {
73 env_.close();
74 return *this;
75 }
76
77 SEnv&
78 enableFeature(uint256 const feature)
79 {
80 env_.enableFeature(feature);
81 return *this;
82 }
83
84 SEnv&
85 disableFeature(uint256 const feature)
86 {
87 env_.app().config().features.erase(feature);
88 return *this;
89 }
90
91 template <class Arg, class... Args>
92 SEnv&
93 fund(STAmount const& amount, Arg const& arg, Args const&... args)
94 {
95 env_.fund(amount, arg, args...);
96 return *this;
97 }
98
99 template <class JsonValue, class... FN>
100 SEnv&
101 tx(JsonValue&& jv, FN const&... fN)
102 {
103 env_(std::forward<JsonValue>(jv), fN...);
104 return *this;
105 }
106
107 template <class... FN>
108 SEnv&
109 multiTx(jtx::JValueVec&& jvv, FN const&... fN)
110 {
111 for (auto const& jv : jvv)
112 env_(jv, fN...);
113 return *this;
114 }
115
116 TER
117 ter() const
118 {
119 return env_.ter();
120 }
121
123 balance(jtx::Account const& account) const
124 {
125 return env_.balance(account).value();
126 }
127
129 balance(jtx::Account const& account, Issue const& issue) const
130 {
131 return env_.balance(account, issue).value();
132 }
133
136 {
137 return env_.current()->fees().accountReserve(count);
138 }
139
142 {
143 return env_.current()->fees().base;
144 }
145
147 account(jtx::Account const& account)
148 {
149 return env_.le(account);
150 }
151
153 bridge(Json::Value const& jvb)
154 {
155 STXChainBridge b(jvb);
156
157 auto tryGet =
159 if (auto r = env_.le(keylet::bridge(b, ct)))
160 {
161 if ((*r)[sfXChainBridge] == b)
162 return r;
163 }
164 return nullptr;
165 };
166 if (auto r = tryGet(STXChainBridge::ChainType::locking))
167 return r;
169 }
170
173 {
174 return (*bridge(jvb))[sfXChainAccountClaimCount];
175 }
176
179 {
180 return (*bridge(jvb))[sfXChainClaimID];
181 }
182
185 {
187 }
188
191 {
192 return env_.le(
194 }
195};
196
197// XEnv class used for XChain tests. The only difference with SEnv<T> is that it
198// funds some default accounts, and that it enables `supported_amendments() |
199// FeatureBitset{featureXChainBridge}` by default.
200// -----------------------------------------------------------------------------
201template <class T>
202struct XEnv : public jtx::XChainBridgeObjects, public SEnv<T>
203{
204 XEnv(T& s, bool side = false)
205 : SEnv<T>(
206 s,
207 jtx::envconfig(jtx::port_increment, side ? 3 : 0),
208 features)
209 {
210 using namespace jtx;
211 STAmount xrp_funds{XRP(10000)};
212
213 if (!side)
214 {
215 this->fund(xrp_funds, mcDoor, mcAlice, mcBob, mcCarol, mcGw);
216
217 // Signer's list must match the attestation signers
218 // env_(jtx::signers(mcDoor, quorum, signers));
219 for (auto& s : signers)
220 this->fund(xrp_funds, s.account);
221 }
222 else
223 {
224 this->fund(
225 xrp_funds,
226 scDoor,
227 scAlice,
228 scBob,
229 scCarol,
230 scGw,
232 scReward);
233
234 for (auto& ra : payees)
235 this->fund(xrp_funds, ra);
236
237 for (auto& s : signers)
238 this->fund(xrp_funds, s.account);
239
240 // Signer's list must match the attestation signers
241 // env_(jtx::signers(Account::master, quorum, signers));
242 }
243 this->close();
244 }
245};
246
247// Tracks the xrp balance for one account
248template <class T>
250{
254
255 Balance(T& env, jtx::Account const& account) : account_(account), env_(env)
256 {
257 startAmount = env_.balance(account_);
258 }
259
261 diff() const
262 {
263 return env_.balance(account_) - startAmount;
264 }
265};
266
267// Tracks the xrp balance for multiple accounts involved in a crosss-chain
268// transfer
269template <class T>
271{
273
276 balance payor_; // pays the rewards
277 std::vector<balance> reward_accounts; // receives the reward
279
281 T& env,
282 jtx::Account const& from_acct,
283 jtx::Account const& to_acct,
284 jtx::Account const& payor,
285 jtx::Account const* payees,
286 size_t num_payees,
287 bool withClaim)
288 : from_(env, from_acct)
289 , to_(env, to_acct)
290 , payor_(env, payor)
291 , reward_accounts([&]() {
293 r.reserve(num_payees);
294 for (size_t i = 0; i < num_payees; ++i)
295 r.emplace_back(env, payees[i]);
296 return r;
297 }())
298 , txFees_(withClaim ? env.env_.current()->fees().base : XRPAmount(0))
299 {
300 }
301
303 T& env,
304 jtx::Account const& from_acct,
305 jtx::Account const& to_acct,
306 jtx::Account const& payor,
307 std::vector<jtx::Account> const& payees,
308 bool withClaim)
310 env,
311 from_acct,
312 to_acct,
313 payor,
314 &payees[0],
315 payees.size(),
316 withClaim)
317 {
318 }
319
320 bool
321 payees_received(STAmount const& reward) const
322 {
323 return std::all_of(
324 reward_accounts.begin(),
325 reward_accounts.end(),
326 [&](const balance& b) { return b.diff() == reward; });
327 }
328
329 bool
330 check_most_balances(STAmount const& amt, STAmount const& reward)
331 {
332 return from_.diff() == -amt && to_.diff() == amt &&
333 payees_received(reward);
334 }
335
336 bool
338 STAmount const& amt,
339 STAmount const& reward,
340 bool check_payer = true)
341 {
342 auto reward_cost =
343 multiply(reward, STAmount(reward_accounts.size()), reward.issue());
344 return check_most_balances(amt, reward) &&
345 (!check_payer || payor_.diff() == -(reward_cost + txFees_));
346 }
347
348 bool
350 {
351 return check_most_balances(STAmount(0), STAmount(0)) &&
352 payor_.diff() <= txFees_; // could have paid fee for failed claim
353 }
354};
355
357{
364 uint32_t quorum;
367
368 template <class ENV>
369 void
370 initBridge(ENV& mcEnv, ENV& scEnv)
371 {
373
374 auto const optAccountCreate = [&]() -> std::optional<STAmount> {
375 if (issueA != xrpIssue() || issueB != xrpIssue())
376 return {};
377 return minAccountCreate;
378 }();
379 mcEnv.tx(bridge_create(doorA, jvb, reward, optAccountCreate))
381 .close();
382
383 scEnv.tx(bridge_create(doorB, jvb, reward, optAccountCreate))
385 .close();
386 }
387};
388
391{
394 {
395 return XEnv(*this).env_.current()->fees().accountReserve(count);
396 }
397
400 {
401 return XEnv(*this).env_.current()->fees().base;
402 }
403
404 void
406 {
407 auto jBridge = create_bridge(mcDoor)[sfXChainBridge.jsonName];
408 bool exceptionPresent = false;
409 try
410 {
411 exceptionPresent = false;
412 [[maybe_unused]] STXChainBridge testBridge1(jBridge);
413 }
414 catch (std::exception& ec)
415 {
416 exceptionPresent = true;
417 }
418
419 BEAST_EXPECT(!exceptionPresent);
420
421 try
422 {
423 exceptionPresent = false;
424 jBridge["Extra"] = 1;
425 [[maybe_unused]] STXChainBridge testBridge2(jBridge);
426 }
427 catch ([[maybe_unused]] std::exception& ec)
428 {
429 exceptionPresent = true;
430 }
431
432 BEAST_EXPECT(exceptionPresent);
433 }
434
435 void
437 {
438 XRPAmount res1 = reserve(1);
439
440 using namespace jtx;
441 testcase("Create Bridge");
442
443 // Normal create_bridge => should succeed
444 XEnv(*this).tx(create_bridge(mcDoor)).close();
445
446 // Bridge not owned by one of the door account.
447 XEnv(*this).tx(
449
450 // Create twice on the same account
451 XEnv(*this)
453 .close()
455
456 // Create USD bridge Alice -> Bob ... should succeed
457 XEnv(*this).tx(
459 mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])),
460 ter(tesSUCCESS));
461
462 // Create USD bridge, Alice is both the locking door and locking issue,
463 // ... should fail.
464 XEnv(*this).tx(
466 mcAlice, bridge(mcAlice, mcAlice["USD"], mcBob, mcBob["USD"])),
468
469 // Bridge where the two door accounts are equal.
470 XEnv(*this).tx(
472 mcBob, bridge(mcBob, mcGw["USD"], mcBob, mcGw["USD"])),
474
475 // Both door accounts are on the same chain. This is not allowed.
476 // Although it doesn't violate any invariants, it's not a useful thing
477 // to do and it complicates the "add claim" transactions.
478 XEnv(*this)
480 mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])))
481 .close()
483 mcBob, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])),
485 .close();
486
487 // Create a bridge on an account with exactly enough balance to
488 // meet the new reserve should succeed
489 XEnv(*this)
490 .fund(res1, mcuDoor) // exact reserve for account + 1 object
491 .close()
493
494 // Create a bridge on an account with no enough balance to meet the
495 // new reserve
496 XEnv(*this)
497 .fund(res1 - 1, mcuDoor) // just short of required reserve
498 .close()
500
501 // Reward amount is non-xrp
502 XEnv(*this).tx(
505
506 // Reward amount is XRP and negative
507 XEnv(*this).tx(
510
511 // Reward amount is 1 xrp => should succeed
513
514 // Min create amount is 1 xrp, mincreate is 1 xrp => should succeed
515 XEnv(*this).tx(
517
518 // Min create amount is non-xrp
519 XEnv(*this).tx(
520 create_bridge(mcDoor, jvb, XRP(1), mcUSD(100)),
522
523 // Min create amount is zero (should fail, currently succeeds)
524 XEnv(*this).tx(
525 create_bridge(mcDoor, jvb, XRP(1), XRP(0)),
527
528 // Min create amount is negative
529 XEnv(*this).tx(
530 create_bridge(mcDoor, jvb, XRP(1), XRP(-1)),
532
533 // coverage test: BridgeCreate::preflight() - create bridge when feature
534 // disabled.
535 {
536 Env env(*this, supported_amendments() - featureXChainBridge);
538 }
539
540 // coverage test: BridgeCreate::preclaim() returns tecNO_ISSUER.
541 XEnv(*this).tx(
543 mcAlice, bridge(mcAlice, mcuAlice["USD"], mcBob, mcBob["USD"])),
545
546 // coverage test: create_bridge transaction with incorrect flag
547 XEnv(*this).tx(
551
552 // coverage test: create_bridge transaction with xchain feature disabled
553 XEnv(*this)
554 .disableFeature(featureXChainBridge)
556 }
557
558 void
560 {
606 using namespace jtx;
607 testcase("Bridge create constraints");
608 XEnv env(*this, true);
609 auto& A = scAlice;
610 auto& B = scBob;
611 auto& C = scCarol;
612 auto AUSD = A["USD"];
613 auto BUSD = B["USD"];
614 auto CUSD = C["USD"];
615 auto GUSD = scGw["USD"];
616 auto AEUR = A["EUR"];
617 auto BEUR = B["EUR"];
618 auto CEUR = C["EUR"];
619 auto GEUR = scGw["EUR"];
620
621 // Accounts to own single brdiges
622 Account const a1("a1");
623 Account const a2("a2");
624 Account const a3("a3");
625 Account const a4("a4");
626 Account const a5("a5");
627 Account const a6("a6");
628
629 env.fund(XRP(10000), a1, a2, a3, a4, a5, a6);
630 env.close();
631
632 // Add a bridge on two different accounts with the same locking and
633 // issuing assets
634 env.tx(create_bridge(a1, bridge(a1, GUSD, B, BUSD))).close();
635 env.tx(create_bridge(a2, bridge(a2, GUSD, B, BUSD))).close();
636
637 // Add the exact same bridge to two different accounts (one locking
638 // account and one issuing)
639 env.tx(create_bridge(a3, bridge(a3, GUSD, a4, a4["USD"]))).close();
640 env.tx(create_bridge(a4, bridge(a3, GUSD, a4, a4["USD"])),
642 .close();
643
644 // Add the exact same bridge to two different accounts (one issuing
645 // account and one locking - opposite order from the test above)
646 env.tx(create_bridge(a5, bridge(a6, GUSD, a5, a5["USD"]))).close();
647 env.tx(create_bridge(a6, bridge(a6, GUSD, a5, a5["USD"])),
649 .close();
650
651 // Test case 1 ~ 5, create bridges
652 auto const goodBridge1 = bridge(A, GUSD, B, BUSD);
653 auto const goodBridge2 = bridge(A, BUSD, C, CUSD);
654 env.tx(create_bridge(B, goodBridge1)).close();
655 // Issuing asset is the same, this is a duplicate
656 env.tx(create_bridge(B, bridge(A, GEUR, B, BUSD)), ter(tecDUPLICATE))
657 .close();
658 env.tx(create_bridge(A, goodBridge2), ter(tesSUCCESS)).close();
659 // Locking asset is the same - this is a duplicate
660 env.tx(create_bridge(A, bridge(A, BUSD, B, BEUR)), ter(tecDUPLICATE))
661 .close();
662 // Locking asset is USD - this is a duplicate even tho it has a
663 // different issuer
664 env.tx(create_bridge(A, bridge(A, CUSD, B, BEUR)), ter(tecDUPLICATE))
665 .close();
666
667 // Test case 6 and 7, commits
668 env.tx(trust(C, BUSD(1000)))
669 .tx(trust(A, BUSD(1000)))
670 .close()
671 .tx(pay(B, C, BUSD(1000)))
672 .close();
673 auto const aBalanceStart = env.balance(A, BUSD);
674 auto const cBalanceStart = env.balance(C, BUSD);
675 env.tx(xchain_commit(C, goodBridge1, 1, BUSD(50))).close();
676 BEAST_EXPECT(env.balance(A, BUSD) - aBalanceStart == BUSD(0));
677 BEAST_EXPECT(env.balance(C, BUSD) - cBalanceStart == BUSD(-50));
678 env.tx(xchain_commit(C, goodBridge2, 1, BUSD(60))).close();
679 BEAST_EXPECT(env.balance(A, BUSD) - aBalanceStart == BUSD(60));
680 BEAST_EXPECT(env.balance(C, BUSD) - cBalanceStart == BUSD(-50 - 60));
681
682 // bridge modify test cases
683 env.tx(bridge_modify(B, goodBridge1, XRP(33), std::nullopt)).close();
684 BEAST_EXPECT((*env.bridge(goodBridge1))[sfSignatureReward] == XRP(33));
685 env.tx(bridge_modify(A, goodBridge2, XRP(44), std::nullopt)).close();
686 BEAST_EXPECT((*env.bridge(goodBridge2))[sfSignatureReward] == XRP(44));
687 }
688
689 void
691 {
692 using namespace jtx;
693 testcase("Create Bridge Matrix");
694
695 // Test all combinations of the following:`
696 // --------------------------------------
697 // - Locking chain is IOU with locking chain door account as issuer
698 // - Locking chain is IOU with issuing chain door account that
699 // exists on the locking chain as issuer
700 // - Locking chain is IOU with issuing chain door account that does
701 // not exists on the locking chain as issuer
702 // - Locking chain is IOU with non-door account (that exists on the
703 // locking chain ledger) as issuer
704 // - Locking chain is IOU with non-door account (that does not exist
705 // exists on the locking chain ledger) as issuer
706 // - Locking chain is XRP
707 // ---------------------------------------------------------------------
708 // - Issuing chain is IOU with issuing chain door account as the
709 // issuer
710 // - Issuing chain is IOU with locking chain door account (that
711 // exists on the issuing chain ledger) as the issuer
712 // - Issuing chain is IOU with locking chain door account (that does
713 // not exist on the issuing chain ledger) as the issuer
714 // - Issuing chain is IOU with non-door account (that exists on the
715 // issuing chain ledger) as the issuer
716 // - Issuing chain is IOU with non-door account (that does not
717 // exists on the issuing chain ledger) as the issuer
718 // - Issuing chain is XRP and issuing chain door account is not the
719 // root account
720 // - Issuing chain is XRP and issuing chain door account is the root
721 // account
722 // ---------------------------------------------------------------------
723 // That's 42 combinations. The only combinations that should succeed
724 // are:
725 // - Locking chain is any IOU,
726 // - Issuing chain is IOU with issuing chain door account as the
727 // issuer
728 // Locking chain is XRP,
729 // - Issuing chain is XRP with issuing chain is the root account.
730 // ---------------------------------------------------------------------
731 Account a("a"), b("b");
732 Issue ia, ib;
733
734 std::tuple lcs{
736 "Locking chain is IOU(locking chain door)",
737 [&](auto& env, bool) {
738 a = mcDoor;
739 ia = mcDoor["USD"];
740 }),
742 "Locking chain is IOU(issuing chain door funded on locking "
743 "chain)",
744 [&](auto& env, bool shouldFund) {
745 a = mcDoor;
746 ia = scDoor["USD"];
747 if (shouldFund)
748 env.fund(XRP(10000), scDoor);
749 }),
751 "Locking chain is IOU(issuing chain door account unfunded "
752 "on locking chain)",
753 [&](auto& env, bool) {
754 a = mcDoor;
755 ia = scDoor["USD"];
756 }),
758 "Locking chain is IOU(bob funded on locking chain)",
759 [&](auto& env, bool) {
760 a = mcDoor;
761 ia = mcGw["USD"];
762 }),
764 "Locking chain is IOU(bob unfunded on locking chain)",
765 [&](auto& env, bool) {
766 a = mcDoor;
767 ia = mcuGw["USD"];
768 }),
769 std::make_pair("Locking chain is XRP", [&](auto& env, bool) {
770 a = mcDoor;
771 ia = xrpIssue();
772 })};
773
774 std::tuple ics{
776 "Issuing chain is IOU(issuing chain door account)",
777 [&](auto& env, bool) {
778 b = scDoor;
779 ib = scDoor["USD"];
780 }),
782 "Issuing chain is IOU(locking chain door funded on issuing "
783 "chain)",
784 [&](auto& env, bool shouldFund) {
785 b = scDoor;
786 ib = mcDoor["USD"];
787 if (shouldFund)
788 env.fund(XRP(10000), mcDoor);
789 }),
791 "Issuing chain is IOU(locking chain door unfunded on "
792 "issuing chain)",
793 [&](auto& env, bool) {
794 b = scDoor;
795 ib = mcDoor["USD"];
796 }),
798 "Issuing chain is IOU(bob funded on issuing chain)",
799 [&](auto& env, bool) {
800 b = scDoor;
801 ib = mcGw["USD"];
802 }),
804 "Issuing chain is IOU(bob unfunded on issuing chain)",
805 [&](auto& env, bool) {
806 b = scDoor;
807 ib = mcuGw["USD"];
808 }),
810 "Issuing chain is XRP and issuing chain door account is "
811 "not the root account",
812 [&](auto& env, bool) {
813 b = scDoor;
814 ib = xrpIssue();
815 }),
817 "Issuing chain is XRP and issuing chain door account is "
818 "the root account ",
819 [&](auto& env, bool) {
820 b = Account::master;
821 ib = xrpIssue();
822 })};
823
824 std::vector<std::pair<int, int>> expected_result{
867
869
870 auto testcase = [&](auto const& lc, auto const& ic) {
871 XEnv mcEnv(*this);
872 XEnv scEnv(*this, true);
873
874 lc.second(mcEnv, true);
875 lc.second(scEnv, false);
876
877 ic.second(mcEnv, false);
878 ic.second(scEnv, true);
879
880 auto const& expected = expected_result[test_result.size()];
881
882 mcEnv.tx(
883 create_bridge(a, bridge(a, ia, b, ib)),
884 ter(TER::fromInt(expected.first)));
885 TER mcTER = mcEnv.env_.ter();
886
887 scEnv.tx(
888 create_bridge(b, bridge(a, ia, b, ib)),
889 ter(TER::fromInt(expected.second)));
890 TER scTER = scEnv.env_.ter();
891
892 bool pass = mcTER == tesSUCCESS && scTER == tesSUCCESS;
893
894 test_result.emplace_back(mcTER, scTER, pass);
895 };
896
897 auto apply_ics = [&](auto const& lc, auto const& ics) {
899 [&](auto const&... ic) { (testcase(lc, ic), ...); }, ics);
900 };
901
902 std::apply([&](auto const&... lc) { (apply_ics(lc, ics), ...); }, lcs);
903
904#if GENERATE_MTX_OUTPUT
905 // optional output of matrix results in markdown format
906 // ----------------------------------------------------
907 std::string fname{std::tmpnam(nullptr)};
908 fname += ".md";
909 std::cout << "Markdown output for matrix test: " << fname << "\n";
910
911 auto print_res = [](auto tup) -> std::string {
912 std::string status = std::string(transToken(std::get<0>(tup))) +
913 " / " + transToken(std::get<1>(tup));
914
915 if (std::get<2>(tup))
916 return status;
917 else
918 {
919 // red
920 return std::string("`") + status + "`";
921 }
922 };
923
924 auto output_table = [&](auto print_res) {
925 size_t test_idx = 0;
926 std::string res;
927 res.reserve(10000); // should be enough :-)
928
929 // first two header lines
930 res += "| `issuing ->` | ";
932 [&](auto const&... ic) {
933 ((res += ic.first, res += " | "), ...);
934 },
935 ics);
936 res += "\n";
937
938 res += "| :--- | ";
940 [&](auto const&... ic) {
941 (((void)ic.first, res += ":---: | "), ...);
942 },
943 ics);
944 res += "\n";
945
946 auto output = [&](auto const& lc, auto const& ic) {
947 res += print_res(test_result[test_idx]);
948 res += " | ";
949 ++test_idx;
950 };
951
952 auto output_ics = [&](auto const& lc, auto const& ics) {
953 res += "| ";
954 res += lc.first;
955 res += " | ";
957 [&](auto const&... ic) { (output(lc, ic), ...); }, ics);
958 res += "\n";
959 };
960
962 [&](auto const&... lc) { (output_ics(lc, ics), ...); }, lcs);
963
964 return res;
965 };
966
967 std::ofstream(fname) << output_table(print_res);
968
969 std::string ter_fname{std::tmpnam(nullptr)};
970 std::cout << "ter output for matrix test: " << ter_fname << "\n";
971
972 std::ofstream ofs(ter_fname);
973 for (auto& t : test_result)
974 {
975 ofs << "{ " << std::string(transToken(std::get<0>(t))) << ", "
976 << std::string(transToken(std::get<1>(t))) << "}\n,";
977 }
978#endif
979 }
980
981 void
983 {
984 using namespace jtx;
985 testcase("Modify Bridge");
986
987 // Changing a non-existent bridge should fail
988 XEnv(*this).tx(
990 mcAlice,
991 bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"]),
992 XRP(2),
993 std::nullopt),
995
996 // must change something
997 // XEnv(*this)
998 // .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1)))
999 // .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(1)),
1000 // ter(temMALFORMED));
1001
1002 // must change something
1003 XEnv(*this)
1004 .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1)))
1005 .close()
1006 .tx(bridge_modify(mcDoor, jvb, {}, {}), ter(temMALFORMED));
1007
1008 // Reward amount is non-xrp
1009 XEnv(*this).tx(
1010 bridge_modify(mcDoor, jvb, mcUSD(2), XRP(10)),
1012
1013 // Reward amount is XRP and negative
1014 XEnv(*this).tx(
1015 bridge_modify(mcDoor, jvb, XRP(-2), XRP(10)),
1017
1018 // Min create amount is non-xrp
1019 XEnv(*this).tx(
1020 bridge_modify(mcDoor, jvb, XRP(2), mcUSD(10)),
1022
1023 // Min create amount is zero
1024 XEnv(*this).tx(
1025 bridge_modify(mcDoor, jvb, XRP(2), XRP(0)),
1027
1028 // Min create amount is negative
1029 XEnv(*this).tx(
1030 bridge_modify(mcDoor, jvb, XRP(2), XRP(-10)),
1032
1033 // First check the regular claim process (without bridge_modify)
1034 for (auto withClaim : {false, true})
1035 {
1036 XEnv mcEnv(*this);
1037 XEnv scEnv(*this, true);
1038
1039 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1040
1043 .close()
1045 .close();
1046
1047 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1048 auto const amt = XRP(1000);
1049 std::uint32_t const claimID = 1;
1050 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1051
1052 BalanceTransfer transfer(
1053 scEnv,
1055 scBob,
1056 scAlice,
1057 &payees[0],
1059 withClaim);
1060
1061 scEnv
1063 scAttester,
1064 jvb,
1065 mcAlice,
1066 amt,
1067 payees,
1068 true,
1069 claimID,
1070 dst,
1071 signers))
1072 .close();
1073
1074 if (withClaim)
1075 {
1076 BEAST_EXPECT(transfer.has_not_happened());
1077
1078 // need to submit a claim transactions
1079 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1080 .close();
1081 }
1082
1083 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
1084 }
1085
1086 // Check that the reward paid from a claim Id was the reward when
1087 // the claim id was created, not the reward since the bridge was
1088 // modified.
1089 for (auto withClaim : {false, true})
1090 {
1091 XEnv mcEnv(*this);
1092 XEnv scEnv(*this, true);
1093
1094 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1095
1098 .close()
1100 .close();
1101
1102 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1103 auto const amt = XRP(1000);
1104 std::uint32_t const claimID = 1;
1105 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1106
1107 // Now modify the reward on the bridge
1108 mcEnv.tx(bridge_modify(mcDoor, jvb, XRP(2), XRP(10))).close();
1109 scEnv.tx(bridge_modify(Account::master, jvb, XRP(2), XRP(10)))
1110 .close();
1111
1112 BalanceTransfer transfer(
1113 scEnv,
1115 scBob,
1116 scAlice,
1117 &payees[0],
1119 withClaim);
1120
1121 scEnv
1123 scAttester,
1124 jvb,
1125 mcAlice,
1126 amt,
1127 payees,
1128 true,
1129 claimID,
1130 dst,
1131 signers))
1132 .close();
1133
1134 if (withClaim)
1135 {
1136 BEAST_EXPECT(transfer.has_not_happened());
1137
1138 // need to submit a claim transactions
1139 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1140 .close();
1141 }
1142
1143 // make sure the reward accounts indeed received the original
1144 // split reward (1 split 5 ways) instead of the updated 2 XRP.
1145 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
1146 }
1147
1148 // Check that the signatures used to verify attestations and decide
1149 // if there is a quorum are the current signer's list on the door
1150 // account, not the signer's list that was in effect when the claim
1151 // id was created.
1152 for (auto withClaim : {false, true})
1153 {
1154 XEnv mcEnv(*this);
1155 XEnv scEnv(*this, true);
1156
1157 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1158
1161 .close()
1163 .close();
1164
1165 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1166 auto const amt = XRP(1000);
1167 std::uint32_t const claimID = 1;
1168 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1169
1170 // change signers - claim should not be processed is the batch
1171 // is signed by original signers
1173 .close();
1174
1175 BalanceTransfer transfer(
1176 scEnv,
1178 scBob,
1179 scAlice,
1180 &payees[0],
1182 withClaim);
1183
1184 // submit claim using outdated signers - should fail
1185 scEnv
1186 .multiTx(
1188 scAttester,
1189 jvb,
1190 mcAlice,
1191 amt,
1192 payees,
1193 true,
1194 claimID,
1195 dst,
1196 signers),
1198 .close();
1199 if (withClaim)
1200 {
1201 // need to submit a claim transactions
1202 scEnv
1203 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
1205 .close();
1206 }
1207
1208 // make sure transfer has not happened as we sent attestations
1209 // using outdated signers
1210 BEAST_EXPECT(transfer.has_not_happened());
1211
1212 // submit claim using current signers - should succeed
1213 scEnv
1215 scAttester,
1216 jvb,
1217 mcAlice,
1218 amt,
1219 payees,
1220 true,
1221 claimID,
1222 dst,
1223 alt_signers))
1224 .close();
1225 if (withClaim)
1226 {
1227 BEAST_EXPECT(transfer.has_not_happened());
1228
1229 // need to submit a claim transactions
1230 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1231 .close();
1232 }
1233
1234 // make sure the transfer went through as we sent attestations
1235 // using new signers
1236 BEAST_EXPECT(
1237 transfer.has_happened(amt, split_reward_quorum, false));
1238 }
1239
1240 // coverage test: bridge_modify transaction with incorrect flag
1241 XEnv(*this)
1243 .close()
1244 .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(2)),
1247
1248 // coverage test: bridge_modify transaction with xchain feature
1249 // disabled
1250 XEnv(*this)
1252 .disableFeature(featureXChainBridge)
1253 .close()
1255
1256 // coverage test: bridge_modify return temSIDECHAIN_NONDOOR_OWNER;
1257 XEnv(*this)
1259 .close()
1260 .tx(bridge_modify(mcAlice, jvb, XRP(1), XRP(2)),
1262
1269 XEnv(*this)
1270 .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20)))
1271 .close()
1273 mcAlice, jvb, scuAlice, XRP(100), reward))
1274 .close()
1275 .tx(bridge_modify(mcDoor, jvb, {}, XRP(2)),
1278 .close()
1279 .tx(bridge_modify(mcDoor, jvb, XRP(3), {}),
1281 .close()
1283 mcAlice, jvb, scuBob, XRP(100), XRP(3)),
1285 .close();
1286 }
1287
1288 void
1290 {
1291 using namespace jtx;
1292 XRPAmount res1 = reserve(1);
1293 XRPAmount tx_fee = txFee();
1294
1295 testcase("Create ClaimID");
1296
1297 // normal bridge create for sanity check with the exact necessary
1298 // account balance
1299 XEnv(*this, true)
1301 .fund(res1, scuAlice) // acct reserve + 1 object
1302 .close()
1304 .close();
1305
1306 // check reward not deducted when claim id is created
1307 {
1308 XEnv xenv(*this, true);
1309
1310 Balance scAlice_bal(xenv, scAlice);
1311
1314 .close();
1315
1316 BEAST_EXPECT(scAlice_bal.diff() == -tx_fee);
1317 }
1318
1319 // Non-existent bridge
1320 XEnv(*this, true)
1322 scAlice,
1323 bridge(mcAlice, mcAlice["USD"], scBob, scBob["USD"]),
1324 reward,
1325 mcAlice),
1327 .close();
1328
1329 // Creating the new object would put the account below the reserve
1330 XEnv(*this, true)
1332 .fund(res1 - xrp_dust, scuAlice) // barely not enough
1333 .close()
1336 .close();
1337
1338 // The specified reward doesn't match the reward on the bridge (test
1339 // by giving the reward amount for the other side, as well as a
1340 // completely non-matching reward)
1341 XEnv(*this, true)
1343 .close()
1347 .close();
1348
1349 // A reward amount that isn't XRP
1350 XEnv(*this, true)
1352 .close()
1355 .close();
1356
1357 // coverage test: xchain_create_claim_id transaction with incorrect
1358 // flag
1359 XEnv(*this, true)
1361 .close()
1365 .close();
1366
1367 // coverage test: xchain_create_claim_id transaction with xchain
1368 // feature disabled
1369 XEnv(*this, true)
1371 .disableFeature(featureXChainBridge)
1372 .close()
1375 .close();
1376 }
1377
1378 void
1380 {
1381 using namespace jtx;
1382 XRPAmount res0 = reserve(0);
1383 XRPAmount tx_fee = txFee();
1384
1385 testcase("Commit");
1386
1387 // Commit to a non-existent bridge
1388 XEnv(*this).tx(
1390
1391 // check that reward not deducted when doing the commit
1392 {
1393 XEnv xenv(*this);
1394
1395 Balance alice_bal(xenv, mcAlice);
1396 auto const amt = XRP(1000);
1397
1398 xenv.tx(create_bridge(mcDoor, jvb))
1399 .close()
1400 .tx(xchain_commit(mcAlice, jvb, 1, amt, scBob))
1401 .close();
1402
1403 STAmount claim_cost = amt;
1404 BEAST_EXPECT(alice_bal.diff() == -(claim_cost + tx_fee));
1405 }
1406
1407 // Commit a negative amount
1408 XEnv(*this)
1410 .close()
1411 .tx(xchain_commit(mcAlice, jvb, 1, XRP(-1), scBob),
1413
1414 // Commit an amount whose issue that does not match the expected
1415 // issue on the bridge (either LockingChainIssue or
1416 // IssuingChainIssue, depending on the chain).
1417 XEnv(*this)
1419 .close()
1420 .tx(xchain_commit(mcAlice, jvb, 1, mcUSD(100), scBob),
1422
1423 // Commit an amount that would put the sender below the required
1424 // reserve (if XRP)
1425 XEnv(*this)
1427 .fund(res0 + one_xrp - xrp_dust, mcuAlice) // barely not enough
1428 .close()
1431
1432 XEnv(*this)
1434 .fund(
1435 res0 + one_xrp + xrp_dust, // "xrp_dust" for tx fees
1436 mcuAlice) // exactly enough => should succeed
1437 .close()
1439
1440 // Commit an amount above the account's balance (for both XRP and
1441 // IOUs)
1442 XEnv(*this)
1444 .fund(res0, mcuAlice) // barely not enough
1445 .close()
1446 .tx(xchain_commit(mcuAlice, jvb, 1, res0 + one_xrp, scBob),
1448
1449 auto jvb_USD = bridge(mcDoor, mcUSD, scGw, scUSD);
1450
1451 // commit sent from iou issuer (mcGw) succeeds - should it?
1452 XEnv(*this)
1453 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1454 .tx(create_bridge(mcDoor, jvb_USD))
1455 .close()
1456 .tx(xchain_commit(mcGw, jvb_USD, 1, mcUSD(1), scBob));
1457
1458 // commit to a door account from the door account. This should fail.
1459 XEnv(*this)
1460 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1461 .tx(create_bridge(mcDoor, jvb_USD))
1462 .close()
1463 .tx(xchain_commit(mcDoor, jvb_USD, 1, mcUSD(1), scBob),
1465
1466 // commit sent from mcAlice which has no IOU balance => should fail
1467 XEnv(*this)
1468 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1469 .tx(create_bridge(mcDoor, jvb_USD))
1470 .close()
1471 .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(1), scBob),
1472 ter(terNO_LINE));
1473
1474 // commit sent from mcAlice which has no IOU balance => should fail
1475 // just changed the destination to scGw (which is the door account
1476 // and may not make much sense)
1477 XEnv(*this)
1478 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1479 .tx(create_bridge(mcDoor, jvb_USD))
1480 .close()
1481 .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(1), scGw),
1482 ter(terNO_LINE));
1483
1484 // commit sent from mcAlice which has a IOU balance => should
1485 // succeed
1486 XEnv(*this)
1487 .tx(trust(mcDoor, mcUSD(10000)))
1488 .tx(trust(mcAlice, mcUSD(10000)))
1489 .close()
1490 .tx(pay(mcGw, mcAlice, mcUSD(10)))
1491 .tx(create_bridge(mcDoor, jvb_USD))
1492 .close()
1493 //.tx(pay(mcAlice, mcDoor, mcUSD(10)));
1494 .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(10), scAlice));
1495
1496 // coverage test: xchain_commit transaction with incorrect flag
1497 XEnv(*this)
1499 .close()
1503
1504 // coverage test: xchain_commit transaction with xchain feature
1505 // disabled
1506 XEnv(*this)
1508 .disableFeature(featureXChainBridge)
1509 .close()
1511 ter(temDISABLED));
1512 }
1513
1514 void
1516 {
1517 using namespace jtx;
1518
1519 testcase("Add Attestation");
1520 XRPAmount res0 = reserve(0);
1521 XRPAmount tx_fee = txFee();
1522
1523 auto multiTtxFee = [&](std::uint32_t m) -> STAmount {
1524 return multiply(tx_fee, STAmount(m), xrpIssue());
1525 };
1526
1527 // Add an attestation to a claim id that has already reached quorum.
1528 // This should succeed and share in the reward.
1529 // note: this is true only when either:
1530 // 1. dest account is not specified, so transfer requires a claim
1531 // 2. or the extra attestation is sent in the same batch as the
1532 // one reaching quorum
1533 for (auto withClaim : {true})
1534 {
1535 XEnv mcEnv(*this);
1536 XEnv scEnv(*this, true);
1537 std::uint32_t const claimID = 1;
1538
1539 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1540
1543 .close()
1545 .close();
1546
1547 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1548
1549 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1550 auto const amt = XRP(1000);
1551 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1552
1553 BalanceTransfer transfer(
1554 scEnv, Account::master, scBob, scAlice, payees, withClaim);
1555
1556 scEnv
1558 scAttester,
1559 jvb,
1560 mcAlice,
1561 amt,
1562 payees,
1563 true,
1564 claimID,
1565 dst,
1566 signers,
1568 .close();
1569 scEnv
1571 scAttester,
1572 jvb,
1573 mcAlice,
1574 amt,
1576 true,
1577 claimID,
1578 dst,
1580 .close();
1581
1582 if (withClaim)
1583 {
1584 BEAST_EXPECT(transfer.has_not_happened());
1585
1586 // need to submit a claim transactions
1587 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1588 .close();
1589 BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted
1590 BEAST_EXPECT(scEnv.claimID(jvb) == claimID);
1591 }
1592
1593 BEAST_EXPECT(transfer.has_happened(amt, split_reward_everyone));
1594 }
1595
1596 // Test that signature weights are correctly handled. Assign
1597 // signature weights of 1,2,4,4 and a quorum of 7. Check that the
1598 // 4,4 signatures reach a quorum, the 1,2,4, reach a quorum, but the
1599 // 4,2, 4,1 and 1,2 do not.
1600
1601 // 1,2,4 => should succeed
1602 for (auto withClaim : {false, true})
1603 {
1604 XEnv mcEnv(*this);
1605 XEnv scEnv(*this, true);
1606
1607 std::uint32_t const quorum_7 = 7;
1608 std::vector<signer> const signers_ = [] {
1609 constexpr int numSigners = 4;
1610 std::uint32_t weights[] = {1, 2, 4, 4};
1611
1612 std::vector<signer> result;
1613 result.reserve(numSigners);
1614 for (int i = 0; i < numSigners; ++i)
1615 {
1616 using namespace std::literals;
1617 auto const a = Account("signer_"s + std::to_string(i));
1618 result.emplace_back(a, weights[i]);
1619 }
1620 return result;
1621 }();
1622
1623 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1624
1626 .tx(jtx::signers(Account::master, quorum_7, signers_))
1627 .close()
1629 .close();
1630 std::uint32_t const claimID = 1;
1631 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1632
1633 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1634 auto const amt = XRP(1000);
1635
1636 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1637
1638 BalanceTransfer transfer(
1639 scEnv,
1641 scBob,
1642 scAlice,
1643 &payees[0],
1644 3,
1645 withClaim);
1646
1647 scEnv
1649 scAttester,
1650 jvb,
1651 mcAlice,
1652 amt,
1653 payees,
1654 true,
1655 claimID,
1656 dst,
1657 signers_,
1658 3))
1659 .close();
1660
1661 if (withClaim)
1662 {
1663 BEAST_EXPECT(transfer.has_not_happened());
1664
1665 // need to submit a claim transactions
1666 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1667 .close();
1668 }
1669
1670 BEAST_EXPECT(!scEnv.claimID(jvb, 1)); // claim id deleted
1671
1672 BEAST_EXPECT(transfer.has_happened(
1673 amt, divide(reward, STAmount(3), reward.issue())));
1674 }
1675
1676 // 4,4 => should succeed
1677 for (auto withClaim : {false, true})
1678 {
1679 XEnv mcEnv(*this);
1680 XEnv scEnv(*this, true);
1681
1682 std::uint32_t const quorum_7 = 7;
1683 std::vector<signer> const signers_ = [] {
1684 constexpr int numSigners = 4;
1685 std::uint32_t weights[] = {1, 2, 4, 4};
1686
1687 std::vector<signer> result;
1688 result.reserve(numSigners);
1689 for (int i = 0; i < numSigners; ++i)
1690 {
1691 using namespace std::literals;
1692 auto const a = Account("signer_"s + std::to_string(i));
1693 result.emplace_back(a, weights[i]);
1694 }
1695 return result;
1696 }();
1697 STAmount const split_reward_ =
1698 divide(reward, STAmount(signers_.size()), reward.issue());
1699
1700 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1701
1703 .tx(jtx::signers(Account::master, quorum_7, signers_))
1704 .close()
1706 .close();
1707 std::uint32_t const claimID = 1;
1708 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1709
1710 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1711 auto const amt = XRP(1000);
1712
1713 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1714
1715 BalanceTransfer transfer(
1716 scEnv,
1718 scBob,
1719 scAlice,
1720 &payees[2],
1721 2,
1722 withClaim);
1723
1724 scEnv
1726 scAttester,
1727 jvb,
1728 mcAlice,
1729 amt,
1730 payees,
1731 true,
1732 claimID,
1733 dst,
1734 signers_,
1735 2,
1736 2))
1737 .close();
1738
1739 if (withClaim)
1740 {
1741 BEAST_EXPECT(transfer.has_not_happened());
1742
1743 // need to submit a claim transactions
1744 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1745 .close();
1746 }
1747
1748 BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted
1749
1750 BEAST_EXPECT(transfer.has_happened(
1751 amt, divide(reward, STAmount(2), reward.issue())));
1752 }
1753
1754 // 1,2 => should fail
1755 for (auto withClaim : {false, true})
1756 {
1757 XEnv mcEnv(*this);
1758 XEnv scEnv(*this, true);
1759
1760 std::uint32_t const quorum_7 = 7;
1761 std::vector<signer> const signers_ = [] {
1762 constexpr int numSigners = 4;
1763 std::uint32_t weights[] = {1, 2, 4, 4};
1764
1765 std::vector<signer> result;
1766 result.reserve(numSigners);
1767 for (int i = 0; i < numSigners; ++i)
1768 {
1769 using namespace std::literals;
1770 auto const a = Account("signer_"s + std::to_string(i));
1771 result.emplace_back(a, weights[i]);
1772 }
1773 return result;
1774 }();
1775
1776 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1777
1779 .tx(jtx::signers(Account::master, quorum_7, signers_))
1780 .close()
1782 .close();
1783
1784 std::uint32_t const claimID = 1;
1785 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1786
1787 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1788 auto const amt = XRP(1000);
1789 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1790
1791 BalanceTransfer transfer(
1792 scEnv,
1794 scBob,
1795 scAlice,
1796 &payees[0],
1797 2,
1798 withClaim);
1799
1800 scEnv
1802 scAttester,
1803 jvb,
1804 mcAlice,
1805 amt,
1806 payees,
1807 true,
1808 claimID,
1809 dst,
1810 signers_,
1811 2))
1812 .close();
1813 if (withClaim)
1814 {
1815 BEAST_EXPECT(transfer.has_not_happened());
1816
1817 // need to submit a claim transactions
1818 scEnv
1819 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
1821 .close();
1822 }
1823
1824 BEAST_EXPECT(
1825 !!scEnv.claimID(jvb, claimID)); // claim id still present
1826 BEAST_EXPECT(transfer.has_not_happened());
1827 }
1828
1829 // 2,4 => should fail
1830 for (auto withClaim : {false, true})
1831 {
1832 XEnv mcEnv(*this);
1833 XEnv scEnv(*this, true);
1834
1835 std::uint32_t const quorum_7 = 7;
1836 std::vector<signer> const signers_ = [] {
1837 constexpr int numSigners = 4;
1838 std::uint32_t weights[] = {1, 2, 4, 4};
1839
1840 std::vector<signer> result;
1841 result.reserve(numSigners);
1842 for (int i = 0; i < numSigners; ++i)
1843 {
1844 using namespace std::literals;
1845 auto const a = Account("signer_"s + std::to_string(i));
1846 result.emplace_back(a, weights[i]);
1847 }
1848 return result;
1849 }();
1850
1851 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1852
1854 .tx(jtx::signers(Account::master, quorum_7, signers_))
1855 .close()
1857 .close();
1858
1859 std::uint32_t const claimID = 1;
1860 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1861
1862 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1863 auto const amt = XRP(1000);
1864
1865 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1866
1867 BalanceTransfer transfer(
1868 scEnv,
1870 scBob,
1871 scAlice,
1872 &payees[1],
1873 2,
1874 withClaim);
1875
1876 scEnv
1878 scAttester,
1879 jvb,
1880 mcAlice,
1881 amt,
1882 payees,
1883 true,
1884 claimID,
1885 dst,
1886 signers_,
1887 2,
1888 1))
1889 .close();
1890
1891 if (withClaim)
1892 {
1893 BEAST_EXPECT(transfer.has_not_happened());
1894
1895 // need to submit a claim transactions
1896 scEnv
1897 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
1899 .close();
1900 }
1901
1902 BEAST_EXPECT(
1903 !!scEnv.claimID(jvb, claimID)); // claim id still present
1904 BEAST_EXPECT(transfer.has_not_happened());
1905 }
1906
1907 // Confirm that account create transactions happen in the correct
1908 // order. If they reach quorum out of order they should not execute
1909 // until all the previous created transactions have occurred.
1910 // Re-adding an attestation should move funds.
1911 {
1912 XEnv mcEnv(*this);
1913 XEnv scEnv(*this, true);
1914 auto const amt = XRP(1000);
1915 auto const amt_plus_reward = amt + reward;
1916
1917 {
1918 Balance door(mcEnv, mcDoor);
1919 Balance carol(mcEnv, mcCarol);
1920
1921 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20)))
1922 .close()
1924 mcAlice, jvb, scuAlice, amt, reward))
1926 mcBob, jvb, scuBob, amt, reward))
1928 mcCarol, jvb, scuCarol, amt, reward))
1929 .close();
1930
1931 BEAST_EXPECT(
1932 door.diff() ==
1933 (multiply(amt_plus_reward, STAmount(3), xrpIssue()) -
1934 tx_fee));
1935 BEAST_EXPECT(carol.diff() == -(amt + reward + tx_fee));
1936 }
1937
1940 .close();
1941
1942 {
1943 // send first batch of account create attest for all 3
1944 // account create
1945 Balance attester(scEnv, scAttester);
1946 Balance door(scEnv, Account::master);
1947
1948 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2))
1950 .multiTx(att_create_acct_vec(2, amt, scuBob, 2))
1951 .close();
1952
1953 BEAST_EXPECT(door.diff() == STAmount(0));
1954 // att_create_acct_vec return vectors of size 2, so 2*3 txns
1955 BEAST_EXPECT(attester.diff() == -multiTtxFee(6));
1956
1957 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // ca claim id present
1958 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present
1959 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present
1960 BEAST_EXPECT(
1961 scEnv.claimCount(jvb) == 0); // claim count still 0
1962 }
1963
1964 {
1965 // complete attestations for 2nd account create => should
1966 // not complete
1967 Balance attester(scEnv, scAttester);
1968 Balance door(scEnv, Account::master);
1969
1970 scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 3, 2))
1971 .close();
1972
1973 BEAST_EXPECT(door.diff() == STAmount(0));
1974 // att_create_acct_vec return vectors of size 3, so 3 txns
1975 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1976
1977 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present
1978 BEAST_EXPECT(
1979 scEnv.claimCount(jvb) == 0); // claim count still 0
1980 }
1981
1982 {
1983 // complete attestations for 3rd account create => should
1984 // not complete
1985 Balance attester(scEnv, scAttester);
1986 Balance door(scEnv, Account::master);
1987
1988 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 3, 2))
1989 .close();
1990
1991 BEAST_EXPECT(door.diff() == STAmount(0));
1992 // att_create_acct_vec return vectors of size 3, so 3 txns
1993 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1994
1995 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present
1996 BEAST_EXPECT(
1997 scEnv.claimCount(jvb) == 0); // claim count still 0
1998 }
1999
2000 {
2001 // complete attestations for 1st account create => account
2002 // should be created
2003 Balance attester(scEnv, scAttester);
2004 Balance door(scEnv, Account::master);
2005
2006 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 3, 1))
2007 .close();
2008
2009 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2010 // att_create_acct_vec return vectors of size 3, so 3 txns
2011 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
2012 BEAST_EXPECT(scEnv.balance(scuAlice) == amt);
2013
2014 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id 1 deleted
2015 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present
2016 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
2017 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count now 1
2018 }
2019
2020 {
2021 // resend attestations for 3rd account create => still
2022 // should not complete
2023 Balance attester(scEnv, scAttester);
2024 Balance door(scEnv, Account::master);
2025
2026 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 3, 2))
2027 .close();
2028
2029 BEAST_EXPECT(door.diff() == STAmount(0));
2030 // att_create_acct_vec return vectors of size 3, so 3 txns
2031 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
2032
2033 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present
2034 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
2035 BEAST_EXPECT(
2036 scEnv.claimCount(jvb) == 1); // claim count still 1
2037 }
2038
2039 {
2040 // resend attestations for 2nd account create => account
2041 // should be created
2042 Balance attester(scEnv, scAttester);
2043 Balance door(scEnv, Account::master);
2044
2045 scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 1)).close();
2046
2047 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2048 BEAST_EXPECT(attester.diff() == -tx_fee);
2049 BEAST_EXPECT(scEnv.balance(scuBob) == amt);
2050
2051 BEAST_EXPECT(!scEnv.caClaimID(jvb, 2)); // claim id 2 deleted
2052 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
2053 BEAST_EXPECT(scEnv.claimCount(jvb) == 2); // claim count now 2
2054 }
2055 {
2056 // resend attestations for 3rc account create => account
2057 // should be created
2058 Balance attester(scEnv, scAttester);
2059 Balance door(scEnv, Account::master);
2060
2061 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1)).close();
2062
2063 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2064 BEAST_EXPECT(attester.diff() == -tx_fee);
2065 BEAST_EXPECT(scEnv.balance(scuCarol) == amt);
2066
2067 BEAST_EXPECT(!scEnv.caClaimID(jvb, 3)); // claim id 3 deleted
2068 BEAST_EXPECT(scEnv.claimCount(jvb) == 3); // claim count now 3
2069 }
2070 }
2071
2072 // Check that creating an account with less than the minimum reserve
2073 // fails.
2074 {
2075 XEnv mcEnv(*this);
2076 XEnv scEnv(*this, true);
2077
2078 auto const amt = res0 - XRP(1);
2079 auto const amt_plus_reward = amt + reward;
2080
2081 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close();
2082
2083 {
2084 Balance door(mcEnv, mcDoor);
2085 Balance carol(mcEnv, mcCarol);
2086
2087 mcEnv
2089 mcCarol, jvb, scuAlice, amt, reward))
2090 .close();
2091
2092 BEAST_EXPECT(door.diff() == amt_plus_reward);
2093 BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee));
2094 }
2095
2098 .close();
2099
2100 Balance attester(scEnv, scAttester);
2101 Balance door(scEnv, Account::master);
2102
2103 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2)).close();
2104 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
2105 BEAST_EXPECT(
2106 scEnv.claimCount(jvb) == 0); // claim count is one less
2107
2108 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2, 2)).close();
2109 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
2110 BEAST_EXPECT(
2111 scEnv.claimCount(jvb) == 1); // claim count was incremented
2112
2113 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
2114 BEAST_EXPECT(door.diff() == -reward);
2115 BEAST_EXPECT(!scEnv.account(scuAlice));
2116 }
2117
2118 // Check that sending funds with an account create txn to an
2119 // existing account works.
2120 {
2121 XEnv mcEnv(*this);
2122 XEnv scEnv(*this, true);
2123
2124 auto const amt = XRP(111);
2125 auto const amt_plus_reward = amt + reward;
2126
2127 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close();
2128
2129 {
2130 Balance door(mcEnv, mcDoor);
2131 Balance carol(mcEnv, mcCarol);
2132
2133 mcEnv
2135 mcCarol, jvb, scAlice, amt, reward))
2136 .close();
2137
2138 BEAST_EXPECT(door.diff() == amt_plus_reward);
2139 BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee));
2140 }
2141
2144 .close();
2145
2146 Balance attester(scEnv, scAttester);
2147 Balance door(scEnv, Account::master);
2148 Balance alice(scEnv, scAlice);
2149
2150 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2)).close();
2151 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
2152 BEAST_EXPECT(
2153 scEnv.claimCount(jvb) == 0); // claim count is one less
2154
2155 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2, 2)).close();
2156 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
2157 BEAST_EXPECT(
2158 scEnv.claimCount(jvb) == 1); // claim count was incremented
2159
2160 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2161 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
2162 BEAST_EXPECT(alice.diff() == amt);
2163 }
2164
2165 // Check that sending funds to an existing account with deposit auth
2166 // set fails for account create transactions.
2167 {
2168 XEnv mcEnv(*this);
2169 XEnv scEnv(*this, true);
2170
2171 auto const amt = XRP(1000);
2172 auto const amt_plus_reward = amt + reward;
2173
2174 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close();
2175
2176 {
2177 Balance door(mcEnv, mcDoor);
2178 Balance carol(mcEnv, mcCarol);
2179
2180 mcEnv
2182 mcCarol, jvb, scAlice, amt, reward))
2183 .close();
2184
2185 BEAST_EXPECT(door.diff() == amt_plus_reward);
2186 BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee));
2187 }
2188
2191 .tx(fset("scAlice", asfDepositAuth)) // set deposit auth
2192 .close();
2193
2194 Balance attester(scEnv, scAttester);
2195 Balance door(scEnv, Account::master);
2196 Balance alice(scEnv, scAlice);
2197
2198 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2)).close();
2199 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
2200 BEAST_EXPECT(
2201 scEnv.claimCount(jvb) == 0); // claim count is one less
2202
2203 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2, 2)).close();
2204 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
2205 BEAST_EXPECT(
2206 scEnv.claimCount(jvb) == 1); // claim count was incremented
2207
2208 BEAST_EXPECT(door.diff() == -reward);
2209 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
2210 BEAST_EXPECT(alice.diff() == STAmount(0));
2211 }
2212
2213 // If an account is unable to pay the reserve, check that it fails.
2214 // [greg todo] I don't know what this should test??
2215
2216 // If an attestation already exists for that server and claim id,
2217 // the new attestation should replace the old attestation
2218 {
2219 XEnv mcEnv(*this);
2220 XEnv scEnv(*this, true);
2221 auto const amt = XRP(1000);
2222 auto const amt_plus_reward = amt + reward;
2223
2224 {
2225 Balance door(mcEnv, mcDoor);
2226 Balance carol(mcEnv, mcCarol);
2227
2228 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20)))
2229 .close()
2231 mcAlice, jvb, scuAlice, amt, reward))
2232 .close() // make sure Alice gets claim #1
2234 mcBob, jvb, scuBob, amt, reward))
2235 .close() // make sure Bob gets claim #2
2237 mcCarol, jvb, scuCarol, amt, reward))
2238 .close(); // and Carol will get claim #3
2239
2240 BEAST_EXPECT(
2241 door.diff() ==
2242 (multiply(amt_plus_reward, STAmount(3), xrpIssue()) -
2243 tx_fee));
2244 BEAST_EXPECT(carol.diff() == -(amt + reward + tx_fee));
2245 }
2246
2247 std::uint32_t const red_quorum = 2;
2249 .tx(jtx::signers(Account::master, red_quorum, signers))
2250 .close();
2251
2252 {
2253 Balance attester(scEnv, scAttester);
2254 Balance door(scEnv, Account::master);
2255 auto const bad_amt = XRP(10);
2256 std::uint32_t txCount = 0;
2257
2258 // send attestations with incorrect amounts to for all 3
2259 // AccountCreate. They will be replaced later
2260 scEnv.multiTx(att_create_acct_vec(1, bad_amt, scuAlice, 1))
2261 .multiTx(att_create_acct_vec(2, bad_amt, scuBob, 1, 2))
2262 .multiTx(att_create_acct_vec(3, bad_amt, scuCarol, 1, 1))
2263 .close();
2264 txCount += 3;
2265
2266 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 1), "claim id 1 created");
2267 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 2), "claim id 2 created");
2268 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 3), "claim id 3 created");
2269
2270 // note: if we send inconsistent attestations in the same
2271 // batch, the transaction errors.
2272
2273 // from now on we send correct attestations
2274 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 1, 0))
2275 .multiTx(att_create_acct_vec(2, amt, scuBob, 1, 2))
2276 .multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 4))
2277 .close();
2278 txCount += 3;
2279
2280 BEAST_EXPECTS(
2281 !!scEnv.caClaimID(jvb, 1), "claim id 1 still there");
2282 BEAST_EXPECTS(
2283 !!scEnv.caClaimID(jvb, 2), "claim id 2 still there");
2284 BEAST_EXPECTS(
2285 !!scEnv.caClaimID(jvb, 3), "claim id 3 still there");
2286 BEAST_EXPECTS(
2287 scEnv.claimCount(jvb) == 0, "No account created yet");
2288
2289 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 1))
2290 .close();
2291 txCount += 1;
2292
2293 BEAST_EXPECTS(
2294 !!scEnv.caClaimID(jvb, 3), "claim id 3 still there");
2295 BEAST_EXPECTS(
2296 scEnv.claimCount(jvb) == 0, "No account created yet");
2297
2298 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 1, 2))
2299 .close();
2300 txCount += 1;
2301
2302 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 deleted");
2303 BEAST_EXPECTS(scEnv.claimCount(jvb) == 1, "scuAlice created");
2304
2305 scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 1, 3))
2306 .multiTx(
2307 att_create_acct_vec(1, amt, scuAlice, 1, 3),
2309 .close();
2310 txCount += 2;
2311
2312 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 2), "claim id 2 deleted");
2313 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 not added");
2314 BEAST_EXPECTS(
2315 scEnv.claimCount(jvb) == 2, "scuAlice & scuBob created");
2316
2317 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 0))
2318 .close();
2319 txCount += 1;
2320
2321 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 3), "claim id 3 deleted");
2322 BEAST_EXPECTS(
2323 scEnv.claimCount(jvb) == 3, "All 3 accounts created");
2324
2325 // because of the division of the rewards among attesters,
2326 // sometimes a couple drops are left over unspent in the
2327 // door account (here 2 drops)
2328 BEAST_EXPECT(
2329 multiply(amt_plus_reward, STAmount(3), xrpIssue()) +
2330 door.diff() <
2331 drops(3));
2332 BEAST_EXPECT(attester.diff() == -multiTtxFee(txCount));
2333 BEAST_EXPECT(scEnv.balance(scuAlice) == amt);
2334 BEAST_EXPECT(scEnv.balance(scuBob) == amt);
2335 BEAST_EXPECT(scEnv.balance(scuCarol) == amt);
2336 }
2337 }
2338
2339 // If attestation moves funds, confirm the claim ledger objects are
2340 // removed (for both account create and "regular" transactions)
2341 // [greg] we do this in all attestation tests
2342
2343 // coverage test: add_attestation transaction with incorrect flag
2344 {
2345 XEnv scEnv(*this, true);
2348 .close()
2350 scAttester,
2351 jvb,
2352 mcAlice,
2353 XRP(1000),
2354 payees[0],
2355 true,
2356 1,
2357 {},
2358 signers[0]),
2361 .close();
2362 }
2363
2364 // coverage test: add_attestation with xchain feature
2365 // disabled
2366 {
2367 XEnv scEnv(*this, true);
2370 .disableFeature(featureXChainBridge)
2371 .close()
2373 scAttester,
2374 jvb,
2375 mcAlice,
2376 XRP(1000),
2377 payees[0],
2378 true,
2379 1,
2380 {},
2381 signers[0]),
2383 .close();
2384 }
2385 }
2386
2387 void
2389 {
2390 using namespace jtx;
2391
2392 testcase("Add Non Batch Claim Attestation");
2393
2394 {
2395 XEnv mcEnv(*this);
2396 XEnv scEnv(*this, true);
2397 std::uint32_t const claimID = 1;
2398
2399 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2400
2403 .close()
2405 .close();
2406
2407 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
2408
2409 Account const dst{scBob};
2410 auto const amt = XRP(1000);
2411 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2412
2413 auto const dstStartBalance = scEnv.env_.balance(dst);
2414
2415 for (int i = 0; i < signers.size(); ++i)
2416 {
2417 auto const att = claim_attestation(
2418 scAttester,
2419 jvb,
2420 mcAlice,
2421 amt,
2422 payees[i],
2423 true,
2424 claimID,
2425 dst,
2426 signers[i]);
2427
2428 TER const expectedTER =
2430 if (i + 1 == quorum)
2431 scEnv.tx(att, ter(expectedTER)).close();
2432 else
2433 scEnv.tx(att, ter(expectedTER)).close();
2434
2435 if (i + 1 < quorum)
2436 BEAST_EXPECT(dstStartBalance == scEnv.env_.balance(dst));
2437 else
2438 BEAST_EXPECT(
2439 dstStartBalance + amt == scEnv.env_.balance(dst));
2440 }
2441 BEAST_EXPECT(dstStartBalance + amt == scEnv.env_.balance(dst));
2442 }
2443
2444 {
2461 XEnv mcEnv(*this);
2462 XEnv scEnv(*this, true);
2463 auto const amt = XRP(1000);
2464 std::uint32_t const claimID = 1;
2465
2466 for (auto i = 0; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS - 2; ++i)
2467 scEnv.fund(amt, alt_signers[i].account);
2468
2469 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2470
2473 .close()
2475 .close();
2476
2477 Account const dst{scBob};
2478 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2479 auto const dstStartBalance = scEnv.env_.balance(dst);
2480
2481 {
2482 // G1: master key
2483 auto att = claim_attestation(
2484 scAttester,
2485 jvb,
2486 mcAlice,
2487 amt,
2488 payees[0],
2489 true,
2490 claimID,
2491 dst,
2492 alt_signers[0]);
2493 scEnv.tx(att).close();
2494 }
2495 {
2496 // G2: regular key
2497 // alt_signers[0] is the regular key of alt_signers[1]
2498 // There should be 2 attestations after the transaction
2499 scEnv
2500 .tx(jtx::regkey(
2501 alt_signers[1].account, alt_signers[0].account))
2502 .close();
2503 auto att = claim_attestation(
2504 scAttester,
2505 jvb,
2506 mcAlice,
2507 amt,
2508 payees[1],
2509 true,
2510 claimID,
2511 dst,
2512 alt_signers[0]);
2513 att[sfAttestationSignerAccount.getJsonName()] =
2514 alt_signers[1].account.human();
2515 scEnv.tx(att).close();
2516 }
2517 {
2518 // B3: public key and non-exist (unfunded) account mismatch
2519 // G3: public key and non-exist (unfunded) account match
2520 auto const unfundedSigner1 =
2522 auto const unfundedSigner2 =
2524 auto att = claim_attestation(
2525 scAttester,
2526 jvb,
2527 mcAlice,
2528 amt,
2530 true,
2531 claimID,
2532 dst,
2533 unfundedSigner1);
2534 att[sfAttestationSignerAccount.getJsonName()] =
2535 unfundedSigner2.account.human();
2537 .close();
2538 att[sfAttestationSignerAccount.getJsonName()] =
2539 unfundedSigner1.account.human();
2540 scEnv.tx(att).close();
2541 }
2542 {
2543 // B2: single item signer list
2544 std::vector<signer> tempSignerList = {signers[0]};
2545 scEnv.tx(
2546 jtx::signers(alt_signers[2].account, 1, tempSignerList));
2547 auto att = claim_attestation(
2548 scAttester,
2549 jvb,
2550 mcAlice,
2551 amt,
2552 payees[2],
2553 true,
2554 claimID,
2555 dst,
2556 tempSignerList.front());
2557 att[sfAttestationSignerAccount.getJsonName()] =
2558 alt_signers[2].account.human();
2560 .close();
2561 }
2562 {
2563 // B1: disabled master key
2564 scEnv.tx(fset(alt_signers[2].account, asfDisableMaster, 0));
2565 auto att = claim_attestation(
2566 scAttester,
2567 jvb,
2568 mcAlice,
2569 amt,
2570 payees[2],
2571 true,
2572 claimID,
2573 dst,
2574 alt_signers[2]);
2576 .close();
2577 }
2578 {
2579 // --B4: not on signer list
2580 auto att = claim_attestation(
2581 scAttester,
2582 jvb,
2583 mcAlice,
2584 amt,
2585 payees[0],
2586 true,
2587 claimID,
2588 dst,
2589 signers[0]);
2590 scEnv.tx(att, ter(tecNO_PERMISSION)).close();
2591 }
2592 {
2593 // --B5: missing sfAttestationSignerAccount field
2594 // Then submit the one with the field. Should rearch quorum.
2595 auto att = claim_attestation(
2596 scAttester,
2597 jvb,
2598 mcAlice,
2599 amt,
2600 payees[3],
2601 true,
2602 claimID,
2603 dst,
2604 alt_signers[3]);
2605 att.removeMember(sfAttestationSignerAccount.getJsonName());
2606 scEnv.tx(att, ter(temMALFORMED)).close();
2607 BEAST_EXPECT(dstStartBalance == scEnv.env_.balance(dst));
2608 att[sfAttestationSignerAccount.getJsonName()] =
2609 alt_signers[3].account.human();
2610 scEnv.tx(att).close();
2611 BEAST_EXPECT(dstStartBalance + amt == scEnv.env_.balance(dst));
2612 }
2613 }
2614 }
2615
2616 void
2618 {
2619 using namespace jtx;
2620
2621 testcase("Add Non Batch Account Create Attestation");
2622
2623 XEnv mcEnv(*this);
2624 XEnv scEnv(*this, true);
2625
2626 XRPAmount tx_fee = mcEnv.txFee();
2627
2628 Account a{"a"};
2629 Account doorA{"doorA"};
2630
2631 STAmount funds{XRP(10000)};
2632 mcEnv.fund(funds, a);
2633 mcEnv.fund(funds, doorA);
2634
2635 Account ua{"ua"}; // unfunded account we want to create
2636
2637 BridgeDef xrp_b{
2638 doorA,
2639 xrpIssue(),
2641 xrpIssue(),
2642 XRP(1), // reward
2643 XRP(20), // minAccountCreate
2644 4, // quorum
2645 signers,
2647
2648 xrp_b.initBridge(mcEnv, scEnv);
2649
2650 auto const amt = XRP(777);
2651 auto const amt_plus_reward = amt + xrp_b.reward;
2652 {
2653 Balance bal_doorA(mcEnv, doorA);
2654 Balance bal_a(mcEnv, a);
2655
2656 mcEnv
2658 a, xrp_b.jvb, ua, amt, xrp_b.reward))
2659 .close();
2660
2661 BEAST_EXPECT(bal_doorA.diff() == amt_plus_reward);
2662 BEAST_EXPECT(bal_a.diff() == -(amt_plus_reward + tx_fee));
2663 }
2664
2665 for (int i = 0; i < signers.size(); ++i)
2666 {
2667 auto const att = create_account_attestation(
2668 signers[0].account,
2669 xrp_b.jvb,
2670 a,
2671 amt,
2672 xrp_b.reward,
2673 signers[i].account,
2674 true,
2675 1,
2676 ua,
2677 signers[i]);
2678 TER const expectedTER = i < xrp_b.quorum
2679 ? tesSUCCESS
2681
2682 scEnv.tx(att, ter(expectedTER)).close();
2683 if (i + 1 < xrp_b.quorum)
2684 BEAST_EXPECT(!scEnv.env_.le(ua));
2685 else
2686 BEAST_EXPECT(scEnv.env_.le(ua));
2687 }
2688 BEAST_EXPECT(scEnv.env_.le(ua));
2689 }
2690
2691 void
2693 {
2694 using namespace jtx;
2695
2696 XRPAmount res0 = reserve(0);
2697 XRPAmount tx_fee = txFee();
2698
2699 testcase("Claim");
2700
2701 // Claim where the amount matches what is attested to, to an account
2702 // that exists, and there are enough attestations to reach a quorum
2703 // => should succeed
2704 // -----------------------------------------------------------------
2705 for (auto withClaim : {false, true})
2706 {
2707 XEnv mcEnv(*this);
2708 XEnv scEnv(*this, true);
2709
2710 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2711
2714 .close()
2716 .close();
2717
2718 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2719 auto const amt = XRP(1000);
2720 std::uint32_t const claimID = 1;
2721 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2722
2723 BalanceTransfer transfer(
2724 scEnv,
2726 scBob,
2727 scAlice,
2728 &payees[0],
2730 withClaim);
2731
2732 scEnv
2734 scAttester,
2735 jvb,
2736 mcAlice,
2737 amt,
2738 payees,
2739 true,
2740 claimID,
2741 dst,
2742 signers))
2743 .close();
2744 if (withClaim)
2745 {
2746 BEAST_EXPECT(transfer.has_not_happened());
2747
2748 // need to submit a claim transactions
2749 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
2750 .close();
2751 }
2752
2753 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
2754 }
2755
2756 // Claim with just one attestation signed by the Master key
2757 // => should not succeed
2758 // -----------------------------------------------------------------
2759 for (auto withClaim : {false, true})
2760 {
2761 XEnv mcEnv(*this);
2762 XEnv scEnv(*this, true);
2763
2764 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2765
2766 scEnv
2768 //.tx(jtx::signers(Account::master, quorum, signers))
2769 .close()
2771 .close();
2772
2773 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2774 auto const amt = XRP(1000);
2775 std::uint32_t const claimID = 1;
2776 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2777
2778 BalanceTransfer transfer(
2779 scEnv,
2781 scBob,
2782 scAlice,
2783 &payees[0],
2784 1,
2785 withClaim);
2786
2787 jtx::signer master_signer(Account::master);
2788 scEnv
2790 scAttester,
2791 jvb,
2792 mcAlice,
2793 amt,
2794 payees[0],
2795 true,
2796 claimID,
2797 dst,
2798 master_signer),
2800 .close();
2801
2802 BEAST_EXPECT(transfer.has_not_happened());
2803 }
2804
2805 // Claim with just one attestation signed by a regular key
2806 // associated to the master account
2807 // => should not succeed
2808 // -----------------------------------------------------------------
2809 for (auto withClaim : {false, true})
2810 {
2811 XEnv mcEnv(*this);
2812 XEnv scEnv(*this, true);
2813
2814 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2815
2816 scEnv
2818 //.tx(jtx::signers(Account::master, quorum, signers))
2820 .close()
2822 .close();
2823
2824 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2825 auto const amt = XRP(1000);
2826 std::uint32_t const claimID = 1;
2827 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2828
2829 BalanceTransfer transfer(
2830 scEnv,
2832 scBob,
2833 scAlice,
2834 &payees[0],
2835 1,
2836 withClaim);
2837
2838 jtx::signer master_signer(payees[0]);
2839 scEnv
2841 scAttester,
2842 jvb,
2843 mcAlice,
2844 amt,
2845 payees[0],
2846 true,
2847 claimID,
2848 dst,
2849 master_signer),
2851 .close();
2852
2853 BEAST_EXPECT(transfer.has_not_happened());
2854 }
2855
2856 // Claim against non-existent bridge
2857 // ---------------------------------
2858 for (auto withClaim : {false, true})
2859 {
2860 XEnv mcEnv(*this);
2861 XEnv scEnv(*this, true);
2862
2863 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2864
2865 auto jvb_unknown =
2867
2870 .close()
2872 scAlice, jvb_unknown, reward, mcAlice),
2874 .close();
2875
2876 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2877 auto const amt = XRP(1000);
2878 std::uint32_t const claimID = 1;
2879 mcEnv
2880 .tx(xchain_commit(mcAlice, jvb_unknown, claimID, amt, dst),
2882 .close();
2883
2884 BalanceTransfer transfer(
2885 scEnv, Account::master, scBob, scAlice, payees, withClaim);
2886 scEnv
2888 scAttester,
2889 jvb_unknown,
2890 mcAlice,
2891 amt,
2892 payees[0],
2893 true,
2894 claimID,
2895 dst,
2896 signers[0]),
2898 .close();
2899
2900 if (withClaim)
2901 {
2902 BEAST_EXPECT(transfer.has_not_happened());
2903
2904 // need to submit a claim transactions
2905 scEnv
2906 .tx(xchain_claim(scAlice, jvb_unknown, claimID, amt, scBob),
2908 .close();
2909 }
2910
2911 BEAST_EXPECT(transfer.has_not_happened());
2912 }
2913
2914 // Claim against non-existent claim id
2915 // -----------------------------------
2916 for (auto withClaim : {false, true})
2917 {
2918 XEnv mcEnv(*this);
2919 XEnv scEnv(*this, true);
2920
2921 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2922
2925 .close()
2927 .close();
2928
2929 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2930 auto const amt = XRP(1000);
2931 std::uint32_t const claimID = 1;
2932 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2933
2934 BalanceTransfer transfer(
2935 scEnv, Account::master, scBob, scAlice, payees, withClaim);
2936
2937 // attest using non-existent claim id
2938 scEnv
2940 scAttester,
2941 jvb,
2942 mcAlice,
2943 amt,
2944 payees[0],
2945 true,
2946 999,
2947 dst,
2948 signers[0]),
2950 .close();
2951 if (withClaim)
2952 {
2953 BEAST_EXPECT(transfer.has_not_happened());
2954
2955 // claim using non-existent claim id
2956 scEnv
2957 .tx(xchain_claim(scAlice, jvb, 999, amt, scBob),
2959 .close();
2960 }
2961
2962 BEAST_EXPECT(transfer.has_not_happened());
2963 }
2964
2965 // Claim against a claim id owned by another account
2966 // -------------------------------------------------
2967 for (auto withClaim : {false, true})
2968 {
2969 XEnv mcEnv(*this);
2970 XEnv scEnv(*this, true);
2971
2972 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2973
2976 .close()
2978 .close();
2979
2980 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2981 auto const amt = XRP(1000);
2982 std::uint32_t const claimID = 1;
2983 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2984
2985 BalanceTransfer transfer(
2986 scEnv,
2988 scBob,
2989 scAlice,
2990 &payees[0],
2992 withClaim);
2993
2994 scEnv
2996 scAttester,
2997 jvb,
2998 mcAlice,
2999 amt,
3000 payees,
3001 true,
3002 claimID,
3003 dst,
3004 signers))
3005 .close();
3006 if (withClaim)
3007 {
3008 BEAST_EXPECT(transfer.has_not_happened());
3009
3010 // submit a claim transaction with the wrong account (scGw
3011 // instead of scAlice)
3012 scEnv
3013 .tx(xchain_claim(scGw, jvb, claimID, amt, scBob),
3015 .close();
3016 BEAST_EXPECT(transfer.has_not_happened());
3017 }
3018 else
3019 {
3020 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3021 }
3022 }
3023
3024 // Claim against a claim id with no attestations
3025 // ---------------------------------------------
3026 for (auto withClaim : {false, true})
3027 {
3028 XEnv mcEnv(*this);
3029 XEnv scEnv(*this, true);
3030
3031 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3032
3035 .close()
3037 .close();
3038
3039 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3040 auto const amt = XRP(1000);
3041 std::uint32_t const claimID = 1;
3042 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3043
3044 BalanceTransfer transfer(
3045 scEnv, Account::master, scBob, scAlice, payees, withClaim);
3046
3047 // don't send any attestations
3048
3049 if (withClaim)
3050 {
3051 BEAST_EXPECT(transfer.has_not_happened());
3052
3053 // need to submit a claim transactions
3054 scEnv
3055 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3057 .close();
3058 }
3059
3060 BEAST_EXPECT(transfer.has_not_happened());
3061 }
3062
3063 // Claim against a claim id with attestations, but not enough to
3064 // make a quorum
3065 // --------------------------------------------------------------------
3066 for (auto withClaim : {false, true})
3067 {
3068 XEnv mcEnv(*this);
3069 XEnv scEnv(*this, true);
3070
3071 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3072
3075 .close()
3077 .close();
3078
3079 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3080 auto const amt = XRP(1000);
3081 std::uint32_t const claimID = 1;
3082 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3083
3084 BalanceTransfer transfer(
3085 scEnv, Account::master, scBob, scAlice, payees, withClaim);
3086
3087 auto tooFew = quorum - 1;
3088 scEnv
3090 scAttester,
3091 jvb,
3092 mcAlice,
3093 amt,
3094 payees,
3095 true,
3096 claimID,
3097 dst,
3098 signers,
3099 tooFew))
3100 .close();
3101 if (withClaim)
3102 {
3103 BEAST_EXPECT(transfer.has_not_happened());
3104
3105 // need to submit a claim transactions
3106 scEnv
3107 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3109 .close();
3110 }
3111
3112 BEAST_EXPECT(transfer.has_not_happened());
3113 }
3114
3115 // Claim id of zero
3116 // ----------------
3117 for (auto withClaim : {false, true})
3118 {
3119 XEnv mcEnv(*this);
3120 XEnv scEnv(*this, true);
3121
3122 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3123
3126 .close()
3128 .close();
3129
3130 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3131 auto const amt = XRP(1000);
3132 std::uint32_t const claimID = 1;
3133 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3134
3135 BalanceTransfer transfer(
3136 scEnv, Account::master, scBob, scAlice, payees, withClaim);
3137
3138 scEnv
3139 .multiTx(
3141 scAttester,
3142 jvb,
3143 mcAlice,
3144 amt,
3145 payees,
3146 true,
3147 0,
3148 dst,
3149 signers),
3151 .close();
3152 if (withClaim)
3153 {
3154 BEAST_EXPECT(transfer.has_not_happened());
3155
3156 // need to submit a claim transactions
3157 scEnv
3158 .tx(xchain_claim(scAlice, jvb, 0, amt, scBob),
3160 .close();
3161 }
3162
3163 BEAST_EXPECT(transfer.has_not_happened());
3164 }
3165
3166 // Claim issue that does not match the expected issue on the bridge
3167 // (either LockingChainIssue or IssuingChainIssue, depending on the
3168 // chain). The claim id should already have enough attestations to
3169 // reach a quorum for this amount (for a different issuer).
3170 // ---------------------------------------------------------------------
3171 for (auto withClaim : {true})
3172 {
3173 XEnv mcEnv(*this);
3174 XEnv scEnv(*this, true);
3175
3176 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3177
3180 .close()
3182 .close();
3183
3184 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3185 auto const amt = XRP(1000);
3186 std::uint32_t const claimID = 1;
3187 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3188
3189 BalanceTransfer transfer(
3190 scEnv,
3192 scBob,
3193 scAlice,
3194 &payees[0],
3196 withClaim);
3197
3198 scEnv
3200 scAttester,
3201 jvb,
3202 mcAlice,
3203 amt,
3204 payees,
3205 true,
3206 claimID,
3207 dst,
3208 signers))
3209 .close();
3210
3211 if (withClaim)
3212 {
3213 BEAST_EXPECT(transfer.has_not_happened());
3214
3215 // need to submit a claim transactions
3216 scEnv
3217 .tx(xchain_claim(scAlice, jvb, claimID, scUSD(1000), scBob),
3219 .close();
3220 }
3221
3222 BEAST_EXPECT(transfer.has_not_happened());
3223 }
3224
3225 // Claim to a destination that does not already exist on the chain
3226 // -----------------------------------------------------------------
3227 for (auto withClaim : {true})
3228 {
3229 XEnv mcEnv(*this);
3230 XEnv scEnv(*this, true);
3231
3232 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3233
3236 .close()
3238 .close();
3239
3240 auto dst(withClaim ? std::nullopt : std::optional<Account>{scuBob});
3241 auto const amt = XRP(1000);
3242 std::uint32_t const claimID = 1;
3243 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3244
3245 BalanceTransfer transfer(
3246 scEnv,
3248 scBob,
3249 scAlice,
3250 &payees[0],
3252 withClaim);
3253
3254 scEnv
3256 scAttester,
3257 jvb,
3258 mcAlice,
3259 amt,
3260 payees,
3261 true,
3262 claimID,
3263 dst,
3264 signers))
3265 .close();
3266 if (withClaim)
3267 {
3268 BEAST_EXPECT(transfer.has_not_happened());
3269
3270 // need to submit a claim transactions
3271 scEnv
3272 .tx(xchain_claim(scAlice, jvb, claimID, amt, scuBob),
3273 ter(tecNO_DST))
3274 .close();
3275 }
3276
3277 BEAST_EXPECT(transfer.has_not_happened());
3278 }
3279
3280 // Claim where the claim id owner does not have enough XRP to pay
3281 // the reward
3282 // ------------------------------------------------------------------
3283 for (auto withClaim : {false, true})
3284 {
3285 XEnv mcEnv(*this);
3286 XEnv scEnv(*this, true);
3287
3288 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3289 STAmount huge_reward{XRP(20000)};
3290 BEAST_EXPECT(huge_reward > scEnv.balance(scAlice));
3291
3292 scEnv.tx(create_bridge(Account::master, jvb, huge_reward))
3294 .close()
3295 .tx(xchain_create_claim_id(scAlice, jvb, huge_reward, mcAlice))
3296 .close();
3297
3298 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3299 auto const amt = XRP(1000);
3300 std::uint32_t const claimID = 1;
3301 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3302
3303 BalanceTransfer transfer(
3304 scEnv,
3306 scBob,
3307 scAlice,
3308 &payees[0],
3310 withClaim);
3311
3312 if (withClaim)
3313 {
3314 scEnv
3316 scAttester,
3317 jvb,
3318 mcAlice,
3319 amt,
3320 payees,
3321 true,
3322 claimID,
3323 dst,
3324 signers))
3325 .close();
3326 BEAST_EXPECT(transfer.has_not_happened());
3327
3328 // need to submit a claim transactions
3329 scEnv
3330 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3332 .close();
3333 }
3334 else
3335 {
3336 auto txns = claim_attestations(
3337 scAttester,
3338 jvb,
3339 mcAlice,
3340 amt,
3341 payees,
3342 true,
3343 claimID,
3344 dst,
3345 signers);
3346 for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i)
3347 {
3348 scEnv.tx(txns[i]).close();
3349 }
3350 scEnv.tx(txns.back());
3351 scEnv.close();
3352 // The attestation should succeed, because it adds an
3353 // attestation, but the claim should fail with insufficient
3354 // funds
3355 scEnv
3356 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3358 .close();
3359 }
3360
3361 BEAST_EXPECT(transfer.has_not_happened());
3362 }
3363
3364 // Claim where the claim id owner has enough XRP to pay the reward,
3365 // but it would put his balance below the reserve
3366 // --------------------------------------------------------------------
3367 for (auto withClaim : {false, true})
3368 {
3369 XEnv mcEnv(*this);
3370 XEnv scEnv(*this, true);
3371
3372 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3373
3376 .fund(
3377 res0 + reward,
3378 scuAlice) // just not enough because of fees
3379 .close()
3382 .close();
3383
3384 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3385 auto const amt = XRP(1000);
3386 std::uint32_t const claimID = 1;
3387 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3388
3389 BalanceTransfer transfer(
3390 scEnv, Account::master, scBob, scuAlice, payees, withClaim);
3391
3392 scEnv
3394 scAttester,
3395 jvb,
3396 mcAlice,
3397 amt,
3398 payees[0],
3399 true,
3400 claimID,
3401 dst,
3402 signers[0]),
3404 .close();
3405 if (withClaim)
3406 {
3407 BEAST_EXPECT(transfer.has_not_happened());
3408
3409 // need to submit a claim transactions
3410 scEnv
3411 .tx(xchain_claim(scuAlice, jvb, claimID, amt, scBob),
3413 .close();
3414 }
3415
3416 BEAST_EXPECT(transfer.has_not_happened());
3417 }
3418
3419 // Pay to an account with deposit auth set
3420 // ---------------------------------------
3421 for (auto withClaim : {false, true})
3422 {
3423 XEnv mcEnv(*this);
3424 XEnv scEnv(*this, true);
3425
3426 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3427
3430 .tx(fset("scBob", asfDepositAuth)) // set deposit auth
3431 .close()
3433 .close();
3434
3435 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3436 auto const amt = XRP(1000);
3437 std::uint32_t const claimID = 1;
3438 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3439
3440 BalanceTransfer transfer(
3441 scEnv,
3443 scBob,
3444 scAlice,
3445 &payees[0],
3447 withClaim);
3448 auto txns = claim_attestations(
3449 scAttester,
3450 jvb,
3451 mcAlice,
3452 amt,
3453 payees,
3454 true,
3455 claimID,
3456 dst,
3457 signers);
3458 for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i)
3459 {
3460 scEnv.tx(txns[i]).close();
3461 }
3462 if (withClaim)
3463 {
3464 scEnv.tx(txns.back()).close();
3465
3466 BEAST_EXPECT(transfer.has_not_happened());
3467
3468 // need to submit a claim transactions
3469 scEnv
3470 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3472 .close();
3473
3474 // the transfer failed, but check that we can still use the
3475 // claimID with a different account
3476 Balance scCarol_bal(scEnv, scCarol);
3477
3478 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol))
3479 .close();
3480 BEAST_EXPECT(scCarol_bal.diff() == amt);
3481 }
3482 else
3483 {
3484 scEnv.tx(txns.back()).close();
3485 scEnv
3486 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3488 .close();
3489 // A way would be to remove deposit auth and resubmit the
3490 // attestations (even though the witness servers won't do
3491 // it)
3492 scEnv
3493 .tx(fset("scBob", 0, asfDepositAuth)) // clear deposit auth
3494 .close();
3495
3496 Balance scBob_bal(scEnv, scBob);
3497 scEnv.tx(txns.back()).close();
3498 BEAST_EXPECT(scBob_bal.diff() == amt);
3499 }
3500 }
3501
3502 // Pay to an account with Destination Tag set
3503 // ------------------------------------------
3504 for (auto withClaim : {false, true})
3505 {
3506 XEnv mcEnv(*this);
3507 XEnv scEnv(*this, true);
3508
3509 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3510
3513 .tx(fset("scBob", asfRequireDest)) // set dest tag
3514 .close()
3516 .close();
3517
3518 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3519 auto const amt = XRP(1000);
3520 std::uint32_t const claimID = 1;
3521 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3522
3523 BalanceTransfer transfer(
3524 scEnv,
3526 scBob,
3527 scAlice,
3528 &payees[0],
3530 withClaim);
3531 auto txns = claim_attestations(
3532 scAttester,
3533 jvb,
3534 mcAlice,
3535 amt,
3536 payees,
3537 true,
3538 claimID,
3539 dst,
3540 signers);
3541 for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i)
3542 {
3543 scEnv.tx(txns[i]).close();
3544 }
3545 if (withClaim)
3546 {
3547 scEnv.tx(txns.back()).close();
3548 BEAST_EXPECT(transfer.has_not_happened());
3549
3550 // need to submit a claim transactions
3551 scEnv
3552 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3554 .close();
3555
3556 // the transfer failed, but check that we can still use the
3557 // claimID with a different account
3558 Balance scCarol_bal(scEnv, scCarol);
3559
3560 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol))
3561 .close();
3562 BEAST_EXPECT(scCarol_bal.diff() == amt);
3563 }
3564 else
3565 {
3566 scEnv.tx(txns.back()).close();
3567 scEnv
3568 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3570 .close();
3571 // A way would be to remove the destination tag requirement
3572 // and resubmit the attestations (even though the witness
3573 // servers won't do it)
3574 scEnv
3575 .tx(fset("scBob", 0, asfRequireDest)) // clear dest tag
3576 .close();
3577
3578 Balance scBob_bal(scEnv, scBob);
3579
3580 scEnv.tx(txns.back()).close();
3581 BEAST_EXPECT(scBob_bal.diff() == amt);
3582 }
3583 }
3584
3585 // Pay to an account with deposit auth set. Check that the attestations
3586 // are still validated and that we can used the claimID to transfer the
3587 // funds to a different account (which doesn't have deposit auth set)
3588 // --------------------------------------------------------------------
3589 {
3590 XEnv mcEnv(*this);
3591 XEnv scEnv(*this, true);
3592
3593 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3594
3597 .tx(fset("scBob", asfDepositAuth)) // set deposit auth
3598 .close()
3600 .close();
3601
3602 auto dst(std::optional<Account>{scBob});
3603 auto const amt = XRP(1000);
3604 std::uint32_t const claimID = 1;
3605 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3606
3607 // we should be able to submit the attestations, but the transfer
3608 // should not occur because dest account has deposit auth set
3609 Balance scBob_bal(scEnv, scBob);
3610
3612 scAttester,
3613 jvb,
3614 mcAlice,
3615 amt,
3616 payees,
3617 true,
3618 claimID,
3619 dst,
3620 signers));
3621 BEAST_EXPECT(scBob_bal.diff() == STAmount(0));
3622
3623 // Check that check that we still can use the claimID to transfer
3624 // the amount to a different account
3625 Balance scCarol_bal(scEnv, scCarol);
3626
3627 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol)).close();
3628 BEAST_EXPECT(scCarol_bal.diff() == amt);
3629 }
3630
3631 // Claim where the amount different from what is attested to
3632 // ---------------------------------------------------------
3633 for (auto withClaim : {true})
3634 {
3635 XEnv mcEnv(*this);
3636 XEnv scEnv(*this, true);
3637
3638 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3639
3642 .close()
3644 .close();
3645
3646 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3647 auto const amt = XRP(1000);
3648 std::uint32_t const claimID = 1;
3649 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3650
3651 BalanceTransfer transfer(
3652 scEnv,
3654 scBob,
3655 scAlice,
3656 &payees[0],
3658 withClaim);
3660 scAttester,
3661 jvb,
3662 mcAlice,
3663 amt,
3664 payees,
3665 true,
3666 claimID,
3667 dst,
3668 signers));
3669 if (withClaim)
3670 {
3671 BEAST_EXPECT(transfer.has_not_happened());
3672
3673 // claim wrong amount
3674 scEnv
3675 .tx(xchain_claim(scAlice, jvb, claimID, one_xrp, scBob),
3677 .close();
3678 }
3679
3680 BEAST_EXPECT(transfer.has_not_happened());
3681 }
3682
3683 // Verify that rewards are paid from the account that owns the claim
3684 // id
3685 // --------------------------------------------------------------------
3686 for (auto withClaim : {false, true})
3687 {
3688 XEnv mcEnv(*this);
3689 XEnv scEnv(*this, true);
3690
3691 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3692
3695 .close()
3697 .close();
3698
3699 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3700 auto const amt = XRP(1000);
3701 std::uint32_t const claimID = 1;
3702 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3703
3704 BalanceTransfer transfer(
3705 scEnv,
3707 scBob,
3708 scAlice,
3709 &payees[0],
3711 withClaim);
3712 Balance scAlice_bal(scEnv, scAlice);
3714 scAttester,
3715 jvb,
3716 mcAlice,
3717 amt,
3718 payees,
3719 true,
3720 claimID,
3721 dst,
3722 signers));
3723
3724 STAmount claim_cost = reward;
3725
3726 if (withClaim)
3727 {
3728 BEAST_EXPECT(transfer.has_not_happened());
3729
3730 // need to submit a claim transactions
3731 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3732 .close();
3733 claim_cost += tx_fee;
3734 }
3735
3736 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3737 BEAST_EXPECT(
3738 scAlice_bal.diff() == -claim_cost); // because reward % 4 == 0
3739 }
3740
3741 // Verify that if a reward is not evenly divisible among the reward
3742 // accounts, the remaining amount goes to the claim id owner.
3743 // ----------------------------------------------------------------
3744 for (auto withClaim : {false, true})
3745 {
3746 XEnv mcEnv(*this);
3747 XEnv scEnv(*this, true);
3748
3750
3753 .close()
3755 .close();
3756
3757 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3758 auto const amt = XRP(1000);
3759 std::uint32_t const claimID = 1;
3760 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3761
3762 BalanceTransfer transfer(
3763 scEnv,
3765 scBob,
3766 scAlice,
3767 &payees[0],
3769 withClaim);
3770 Balance scAlice_bal(scEnv, scAlice);
3772 scAttester,
3773 jvb,
3774 mcAlice,
3775 amt,
3776 payees,
3777 true,
3778 claimID,
3779 dst,
3780 signers));
3781 STAmount claim_cost = tiny_reward;
3782
3783 if (withClaim)
3784 {
3785 BEAST_EXPECT(transfer.has_not_happened());
3786
3787 // need to submit a claim transactions
3788 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3789 .close();
3790 claim_cost += tx_fee;
3791 }
3792
3793 BEAST_EXPECT(transfer.has_happened(amt, tiny_reward_split));
3794 BEAST_EXPECT(
3795 scAlice_bal.diff() == -(claim_cost - tiny_reward_remainder));
3796 }
3797
3798 // If a reward distribution fails for one of the reward accounts
3799 // (the reward account doesn't exist or has deposit auth set), then
3800 // the txn should still succeed, but that portion should go to the
3801 // claim id owner.
3802 // -------------------------------------------------------------------
3803 for (auto withClaim : {false, true})
3804 {
3805 XEnv mcEnv(*this);
3806 XEnv scEnv(*this, true);
3807
3808 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3809
3810 std::vector<Account> alt_payees{payees.begin(), payees.end() - 1};
3811 alt_payees.back() = Account("inexistent");
3812
3815 .close()
3817 .close();
3818
3819 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3820 auto const amt = XRP(1000);
3821 std::uint32_t const claimID = 1;
3822 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3823
3824 BalanceTransfer transfer(
3825 scEnv,
3827 scBob,
3828 scAlice,
3829 &payees[0],
3831 withClaim);
3833 scAttester,
3834 jvb,
3835 mcAlice,
3836 amt,
3837 alt_payees,
3838 true,
3839 claimID,
3840 dst,
3841 signers));
3842
3843 if (withClaim)
3844 {
3845 BEAST_EXPECT(transfer.has_not_happened());
3846
3847 // need to submit a claim transactions
3848 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3849 .close();
3850 }
3851
3852 // this also checks that only 3 * split_reward was deducted from
3853 // scAlice (the payor account), since we passed alt_payees to
3854 // BalanceTransfer
3855 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3856 }
3857
3858 for (auto withClaim : {false, true})
3859 {
3860 XEnv mcEnv(*this);
3861 XEnv scEnv(*this, true);
3862
3863 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3864 auto& unpaid = payees[UT_XCHAIN_DEFAULT_QUORUM - 1];
3867 .tx(fset(unpaid, asfDepositAuth))
3868 .close()
3870 .close();
3871
3872 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3873 auto const amt = XRP(1000);
3874 std::uint32_t const claimID = 1;
3875 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3876
3877 // balance of last signer should not change (has deposit auth)
3878 Balance last_signer(scEnv, unpaid);
3879
3880 // make sure all signers except the last one get the
3881 // split_reward
3882
3883 BalanceTransfer transfer(
3884 scEnv,
3886 scBob,
3887 scAlice,
3888 &payees[0],
3890 withClaim);
3892 scAttester,
3893 jvb,
3894 mcAlice,
3895 amt,
3896 payees,
3897 true,
3898 claimID,
3899 dst,
3900 signers));
3901
3902 if (withClaim)
3903 {
3904 BEAST_EXPECT(transfer.has_not_happened());
3905
3906 // need to submit a claim transactions
3907 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3908 .close();
3909 }
3910
3911 // this also checks that only 3 * split_reward was deducted from
3912 // scAlice (the payor account), since we passed payees.size() -
3913 // 1 to BalanceTransfer
3914 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3915
3916 // and make sure the account with deposit auth received nothing
3917 BEAST_EXPECT(last_signer.diff() == STAmount(0));
3918 }
3919
3920 // coverage test: xchain_claim transaction with incorrect flag
3921 XEnv(*this, true)
3923 .close()
3924 .tx(xchain_claim(scAlice, jvb, 1, XRP(1000), scBob),
3927 .close();
3928
3929 // coverage test: xchain_claim transaction with xchain feature
3930 // disabled
3931 XEnv(*this, true)
3933 .disableFeature(featureXChainBridge)
3934 .close()
3935 .tx(xchain_claim(scAlice, jvb, 1, XRP(1000), scBob),
3937 .close();
3938
3939 // coverage test: XChainClaim::preclaim - isLockingChain = true;
3940 XEnv(*this)
3942 .close()
3943 .tx(xchain_claim(mcAlice, jvb, 1, XRP(1000), mcBob),
3945 }
3946
3947 void
3949 {
3950 using namespace jtx;
3951
3952 testcase("Bridge Create Account");
3953 XRPAmount tx_fee = txFee();
3954
3955 // coverage test: transferHelper() - dst == src
3956 {
3957 XEnv scEnv(*this, true);
3958
3959 auto const amt = XRP(111);
3960 auto const amt_plus_reward = amt + reward;
3961
3964 .close();
3965
3966 Balance door(scEnv, Account::master);
3967
3968 // scEnv.tx(att_create_acct_batch1(1, amt,
3969 // Account::master)).close();
3971 .close();
3972 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
3973 BEAST_EXPECT(
3974 scEnv.claimCount(jvb) == 0); // claim count is one less
3975
3976 // scEnv.tx(att_create_acct_batch2(1, amt,
3977 // Account::master)).close();
3978 scEnv.multiTx(att_create_acct_vec(1, amt, Account::master, 2, 2))
3979 .close();
3980 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
3981 BEAST_EXPECT(
3982 scEnv.claimCount(jvb) == 1); // claim count was incremented
3983
3984 BEAST_EXPECT(door.diff() == -reward);
3985 }
3986
3987 // Check that creating an account with less than the minimum create
3988 // amount fails.
3989 {
3990 XEnv mcEnv(*this);
3991
3992 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3993
3994 Balance door(mcEnv, mcDoor);
3995 Balance carol(mcEnv, mcCarol);
3996
3997 mcEnv
3999 mcCarol, jvb, scuAlice, XRP(19), reward),
4001 .close();
4002
4003 BEAST_EXPECT(door.diff() == STAmount(0));
4004 BEAST_EXPECT(carol.diff() == -tx_fee);
4005 }
4006
4007 // Check that creating an account with invalid flags fails.
4008 {
4009 XEnv mcEnv(*this);
4010
4011 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4012
4013 Balance door(mcEnv, mcDoor);
4014
4015 mcEnv
4017 mcCarol, jvb, scuAlice, XRP(20), reward),
4020 .close();
4021
4022 BEAST_EXPECT(door.diff() == STAmount(0));
4023 }
4024
4025 // Check that creating an account with the XChainBridge feature
4026 // disabled fails.
4027 {
4028 XEnv mcEnv(*this);
4029
4030 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4031
4032 Balance door(mcEnv, mcDoor);
4033
4034 mcEnv.disableFeature(featureXChainBridge)
4036 mcCarol, jvb, scuAlice, XRP(20), reward),
4038 .close();
4039
4040 BEAST_EXPECT(door.diff() == STAmount(0));
4041 }
4042
4043 // Check that creating an account with a negative amount fails
4044 {
4045 XEnv mcEnv(*this);
4046
4047 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4048
4049 Balance door(mcEnv, mcDoor);
4050
4051 mcEnv
4053 mcCarol, jvb, scuAlice, XRP(-20), reward),
4055 .close();
4056
4057 BEAST_EXPECT(door.diff() == STAmount(0));
4058 }
4059
4060 // Check that creating an account with a negative reward fails
4061 {
4062 XEnv mcEnv(*this);
4063
4064 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4065
4066 Balance door(mcEnv, mcDoor);
4067
4068 mcEnv
4070 mcCarol, jvb, scuAlice, XRP(20), XRP(-1)),
4072 .close();
4073
4074 BEAST_EXPECT(door.diff() == STAmount(0));
4075 }
4076
4077 // Check that door account can't lock funds onto itself
4078 {
4079 XEnv mcEnv(*this);
4080
4081 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4082
4083 Balance door(mcEnv, mcDoor);
4084
4085 mcEnv
4087 mcDoor, jvb, scuAlice, XRP(20), XRP(1)),
4089 .close();
4090
4091 BEAST_EXPECT(door.diff() == -tx_fee);
4092 }
4093
4094 // Check that reward matches the amount specified in bridge
4095 {
4096 XEnv mcEnv(*this);
4097
4098 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4099
4100 Balance door(mcEnv, mcDoor);
4101
4102 mcEnv
4104 mcCarol, jvb, scuAlice, XRP(20), XRP(2)),
4106 .close();
4107
4108 BEAST_EXPECT(door.diff() == STAmount(0));
4109 }
4110 }
4111
4112 void
4114 {
4115 using namespace jtx;
4116 XRPAmount res0 = reserve(0);
4117 XRPAmount tx_fee = txFee();
4118
4119 testcase("Fee dips into reserve");
4120
4121 // commit where the fee dips into the reserve, this should succeed
4122 XEnv(*this)
4124 .fund(res0 + one_xrp + tx_fee - drops(1), mcuAlice)
4125 .close()
4127 ter(tesSUCCESS));
4128
4129 // commit where the commit amount drips into the reserve, this should
4130 // fail
4131 XEnv(*this)
4133 .fund(res0 + one_xrp - drops(1), mcuAlice)
4134 .close()
4137
4138 auto const minAccountCreate = XRP(20);
4139
4140 // account create commit where the fee dips into the reserve,
4141 // this should succeed
4142 XEnv(*this)
4143 .tx(create_bridge(mcDoor, jvb, reward, minAccountCreate))
4144 .fund(
4145 res0 + tx_fee + minAccountCreate + reward - drops(1), mcuAlice)
4146 .close()
4148 mcuAlice, jvb, scuAlice, minAccountCreate, reward),
4149 ter(tesSUCCESS));
4150
4151 // account create commit where the commit dips into the reserve,
4152 // this should fail
4153 XEnv(*this)
4154 .tx(create_bridge(mcDoor, jvb, reward, minAccountCreate))
4155 .fund(res0 + minAccountCreate + reward - drops(1), mcuAlice)
4156 .close()
4158 mcuAlice, jvb, scuAlice, minAccountCreate, reward),
4160 }
4161
4162 void
4164 {
4165 using namespace jtx;
4166
4167 testcase("Bridge Delete Door Account");
4168
4169 auto const acctDelFee{
4170 drops(XEnv(*this).env_.current()->fees().increment)};
4171
4172 // Deleting an account that owns bridge should fail
4173 {
4174 XEnv mcEnv(*this);
4175
4176 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1))).close();
4177
4178 // We don't allow an account to be deleted if its sequence
4179 // number is within 256 of the current ledger.
4180 for (size_t i = 0; i < 256; ++i)
4181 mcEnv.close();
4182
4183 // try to delete mcDoor, send funds to mcAlice
4184 mcEnv.tx(
4186 fee(acctDelFee),
4188 }
4189
4190 // Deleting an account that owns a claim id should fail
4191 {
4192 XEnv scEnv(*this, true);
4193
4195 .close()
4197 .close();
4198
4199 // We don't allow an account to be deleted if its sequence
4200 // number is within 256 of the current ledger.
4201 for (size_t i = 0; i < 256; ++i)
4202 scEnv.close();
4203
4204 // try to delete scAlice, send funds to scBob
4205 scEnv.tx(
4207 fee(acctDelFee),
4209 }
4210 }
4211
4212 void
4214 {
4215 using namespace jtx;
4216
4217 testcase("Bad attestations");
4218 {
4219 // Create a bridge and add an attestation with a bad public key
4220 XEnv scEnv(*this, true);
4221 std::uint32_t const claimID = 1;
4223 auto const amt = XRP(1000);
4226 .close();
4228 .close();
4229 auto jvAtt = claim_attestation(
4230 scAttester,
4231 jvb,
4232 mcAlice,
4233 amt,
4235 true,
4236 claimID,
4237 dst,
4239 {
4240 // Change to an invalid keytype
4241 auto k = jvAtt["PublicKey"].asString();
4242 k.at(1) = '9';
4243 jvAtt["PublicKey"] = k;
4244 }
4245 scEnv.tx(jvAtt, ter(temMALFORMED)).close();
4246 }
4247 {
4248 // Create a bridge and add an create account attestation with a bad
4249 // public key
4250 XEnv scEnv(*this, true);
4251 std::uint32_t const createCount = 1;
4252 Account dst{scBob};
4253 auto const amt = XRP(1000);
4254 auto const rewardAmt = XRP(1);
4257 .close();
4258 auto jvAtt = create_account_attestation(
4259 scAttester,
4260 jvb,
4261 mcAlice,
4262 amt,
4263 rewardAmt,
4265 true,
4266 createCount,
4267 dst,
4269 {
4270 // Change to an invalid keytype
4271 auto k = jvAtt["PublicKey"].asString();
4272 k.at(1) = '9';
4273 jvAtt["PublicKey"] = k;
4274 }
4275 scEnv.tx(jvAtt, ter(temMALFORMED)).close();
4276 }
4277 }
4278
4279 void
4280 run() override
4281 {
4297 }
4298};
4299
4300// -----------------------------------------------------------
4301// -----------------------------------------------------------
4304{
4305private:
4306 static constexpr size_t num_signers = 5;
4307
4308 // --------------------------------------------------
4309 enum class WithClaim { no, yes };
4311 {
4316 bool a2b; // direction of transfer
4318 uint32_t claim_id{0};
4320 };
4321
4323 {
4328 bool a2b;
4329 uint32_t claim_id{0};
4331 };
4332
4334 using BridgeID = BridgeDef const*;
4335
4336 // tracking chain state
4337 // --------------------
4339 {
4342
4343 void
4344 init(ENV& env, jtx::Account const& acct)
4345 {
4346 startAmount = env.balance(acct);
4348 }
4349
4350 bool
4351 verify(ENV& env, jtx::Account const& acct) const
4352 {
4353 STAmount diff{env.balance(acct) - startAmount};
4354 bool check = diff == expectedDiff;
4355 return check;
4356 }
4357 };
4358
4359 // --------------------------------------------------
4361 {
4365
4367 : env(env), tx_fee(env.env_.current()->fees().base)
4368 {
4369 }
4370
4371 void
4372 sendAttestations(size_t signer_idx, BridgeID bridge, ClaimVec& claims)
4373 {
4374 for (auto const& c : claims)
4375 {
4376 env.tx(c).close();
4377 spendFee(bridge->signers[signer_idx].account);
4378 }
4379 claims.clear();
4380 }
4381
4382 uint32_t
4384 size_t signer_idx,
4386 CreateClaimVec& claims)
4387 {
4388 size_t num_successful = 0;
4389 for (auto const& c : claims)
4390 {
4391 env.tx(c).close();
4392 if (env.ter() == tesSUCCESS)
4393 {
4394 counters[bridge].signers.push_back(signer_idx);
4395 num_successful++;
4396 }
4397 spendFee(bridge->signers[signer_idx].account);
4398 }
4399 claims.clear();
4400 return num_successful;
4401 }
4402
4403 void
4405 {
4406 bool callback_called;
4407
4408 // we have this "do {} while" loop because we want to process
4409 // all the account create which can reach quorum at this time
4410 // stamp.
4411 do
4412 {
4413 callback_called = false;
4414 for (size_t i = 0; i < signers_attns.size(); ++i)
4415 {
4416 for (auto& [bridge, claims] : signers_attns[i])
4417 {
4418 sendAttestations(i, bridge, claims.xfer_claims);
4419
4420 auto& c = counters[bridge];
4421 auto& create_claims =
4422 claims.create_claims[c.claim_count];
4423 auto num_attns = create_claims.size();
4424 if (num_attns)
4425 {
4426 c.num_create_attn_sent += sendCreateAttestations(
4427 i, bridge, create_claims);
4428 }
4429 assert(claims.create_claims[c.claim_count].empty());
4430 }
4431 }
4432 for (auto& [bridge, c] : counters)
4433 {
4434 if (c.num_create_attn_sent >= bridge->quorum)
4435 {
4436 callback_called = true;
4437 c.create_callbacks[c.claim_count](c.signers);
4438 ++c.claim_count;
4439 c.num_create_attn_sent = 0;
4440 c.signers.clear();
4441 }
4442 }
4443 } while (callback_called);
4444 }
4445
4446 void
4447 init(jtx::Account const& acct)
4448 {
4449 accounts[acct].init(env, acct);
4450 }
4451
4452 void
4454 jtx::Account const& acct,
4455 STAmount amt,
4456 std::uint64_t divisor = 1)
4457 {
4458 if (amt.issue() != xrpIssue())
4459 return;
4460 auto it = accounts.find(acct);
4461 if (it == accounts.end())
4462 {
4463 accounts[acct].init(env, acct);
4464 // we just looked up the account, so expectedDiff == 0
4465 }
4466 else
4467 {
4468 it->second.expectedDiff +=
4469 (divisor == 1 ? amt
4470 : divide(
4471 amt,
4472 STAmount(amt.issue(), divisor),
4473 amt.issue()));
4474 }
4475 }
4476
4477 void
4478 spend(jtx::Account const& acct, STAmount amt, std::uint64_t times = 1)
4479 {
4480 if (amt.issue() != xrpIssue())
4481 return;
4482 receive(
4483 acct,
4484 times == 1
4485 ? -amt
4486 : -multiply(
4487 amt, STAmount(amt.issue(), times), amt.issue()));
4488 }
4489
4490 void
4491 transfer(jtx::Account const& from, jtx::Account const& to, STAmount amt)
4492 {
4493 spend(from, amt);
4494 receive(to, amt);
4495 }
4496
4497 void
4498 spendFee(jtx::Account const& acct, size_t times = 1)
4499 {
4500 spend(acct, tx_fee, times);
4501 }
4502
4503 bool
4504 verify() const
4505 {
4506 for (auto const& [acct, state] : accounts)
4507 if (!state.verify(env, acct))
4508 return false;
4509 return true;
4510 }
4511
4513 {
4516
4517 uint32_t claim_id{0};
4518 uint32_t create_count{0}; // for account create. First should be 1
4519 uint32_t claim_count{
4520 0}; // for account create. Increments after quorum for
4521 // current create_count (starts at 1) is reached.
4522
4523 uint32_t num_create_attn_sent{0}; // for current claim_count
4526 };
4527
4528 struct Claims
4529 {
4532 };
4533
4536
4542 };
4543
4545 {
4546 ChainStateTracker(ENV& a_env, ENV& b_env) : a_(a_env), b_(b_env)
4547 {
4548 }
4549
4550 bool
4551 verify() const
4552 {
4553 return a_.verify() && b_.verify();
4554 }
4555
4556 void
4558 {
4561 }
4562
4563 void
4564 init(jtx::Account const& acct)
4565 {
4566 a_.init(acct);
4567 b_.init(acct);
4568 }
4569
4572 };
4573
4574 enum SmState {
4581 };
4582
4583 enum Act_Flags { af_a2b = 1 << 0 };
4584
4585 // --------------------------------------------------
4586 template <class T>
4588 {
4589 public:
4591 const std::shared_ptr<ChainStateTracker>& chainstate,
4592 const BridgeDef& bridge)
4593 : bridge_(bridge), st_(chainstate)
4594 {
4595 }
4596
4599 {
4600 return static_cast<T&>(*this).a2b() ? st_->a_ : st_->b_;
4601 }
4602
4605 {
4606 return static_cast<T&>(*this).a2b() ? st_->b_ : st_->a_;
4607 }
4608
4609 jtx::Account const&
4611 {
4612 return static_cast<T&>(*this).a2b() ? bridge_.doorA : bridge_.doorB;
4613 }
4614
4615 jtx::Account const&
4617 {
4618 return static_cast<T&>(*this).a2b() ? bridge_.doorB : bridge_.doorA;
4619 }
4620
4621 protected:
4624 };
4625
4626 // --------------------------------------------------
4627 class SmCreateAccount : public SmBase<SmCreateAccount>
4628 {
4629 public:
4631
4633 const std::shared_ptr<ChainStateTracker>& chainstate,
4634 const BridgeDef& bridge,
4636 : Base(chainstate, bridge)
4638 , cr(std::move(create))
4639 {
4640 }
4641
4642 bool
4643 a2b() const
4644 {
4645 return cr.a2b;
4646 }
4647
4648 uint32_t
4650 {
4651 ChainStateTrack& st = srcState();
4652 jtx::Account const& srcdoor = srcDoor();
4653
4654 st.env
4657 .close(); // needed for claim_id sequence to be correct'
4658 st.spendFee(cr.from);
4659 st.transfer(cr.from, srcdoor, cr.amt);
4660 st.transfer(cr.from, srcdoor, cr.reward);
4661
4662 return ++st.counters[&bridge_].create_count;
4663 }
4664
4665 void
4666 attest(uint64_t time, uint32_t rnd)
4667 {
4668 ChainStateTrack& st = destState();
4669
4670 // check all signers, but start at a random one
4671 size_t i;
4672 for (i = 0; i < num_signers; ++i)
4673 {
4674 size_t signer_idx = (rnd + i) % num_signers;
4675
4676 if (!(cr.attested[signer_idx]))
4677 {
4678 // enqueue one attestation for this signer
4679 cr.attested[signer_idx] = true;
4680
4681 st.signers_attns[signer_idx][&bridge_]
4682 .create_claims[cr.claim_id - 1]
4683 .emplace_back(create_account_attestation(
4684 bridge_.signers[signer_idx].account,
4685 bridge_.jvb,
4686 cr.from,
4687 cr.amt,
4688 cr.reward,
4689 bridge_.signers[signer_idx].account,
4690 cr.a2b,
4691 cr.claim_id,
4692 cr.to,
4693 bridge_.signers[signer_idx]));
4694 break;
4695 }
4696 }
4697
4698 if (i == num_signers)
4699 return; // did not attest
4700
4701 auto& counters = st.counters[&bridge_];
4702 if (counters.create_callbacks.size() < cr.claim_id)
4703 counters.create_callbacks.resize(cr.claim_id);
4704
4705 auto complete_cb = [&](std::vector<size_t> const& signers) {
4706 auto num_attestors = signers.size();
4707 st.env.close();
4708 assert(
4709 num_attestors <=
4710 std::count(cr.attested.begin(), cr.attested.end(), true));
4711 assert(num_attestors >= bridge_.quorum);
4712 assert(cr.claim_id - 1 == counters.claim_count);
4713
4714 auto r = cr.reward;
4715 auto reward = divide(r, STAmount(num_attestors), r.issue());
4716
4717 for (auto i : signers)
4718 st.receive(bridge_.signers[i].account, reward);
4719
4720 st.spend(dstDoor(), reward, num_attestors);
4721 st.transfer(dstDoor(), cr.to, cr.amt);
4722 st.env.env_.memoize(cr.to);
4724 };
4725
4726 counters.create_callbacks[cr.claim_id - 1] = std::move(complete_cb);
4727 }
4728
4729 SmState
4730 advance(uint64_t time, uint32_t rnd)
4731 {
4732 switch (sm_state)
4733 {
4734 case st_initial:
4737 break;
4738
4739 case st_attesting:
4740 attest(time, rnd);
4741 break;
4742
4743 default:
4744 assert(0);
4745 break;
4746
4747 case st_completed:
4748 break; // will get this once
4749 }
4750 return sm_state;
4751 }
4752
4753 private:
4756 };
4757
4758 // --------------------------------------------------
4759 class SmTransfer : public SmBase<SmTransfer>
4760 {
4761 public:
4763
4765 const std::shared_ptr<ChainStateTracker>& chainstate,
4766 const BridgeDef& bridge,
4767 Transfer xfer)
4768 : Base(chainstate, bridge)
4769 , xfer(std::move(xfer))
4771 {
4772 }
4773
4774 bool
4775 a2b() const
4776 {
4777 return xfer.a2b;
4778 }
4779
4780 uint32_t
4782 {
4783 ChainStateTrack& st = destState();
4784
4785 st.env
4788 .close(); // needed for claim_id sequence to be
4789 // correct'
4790 st.spendFee(xfer.to);
4791 return ++st.counters[&bridge_].claim_id;
4792 }
4793
4794 void
4796 {
4797 ChainStateTrack& st = srcState();
4798 jtx::Account const& srcdoor = srcDoor();
4799
4800 if (xfer.amt.issue() != xrpIssue())
4801 {
4802 st.env.tx(pay(srcdoor, xfer.from, xfer.amt));
4803 st.spendFee(srcdoor);
4804 }
4805 st.env.tx(xchain_commit(
4806 xfer.from,
4807 bridge_.jvb,
4808 xfer.claim_id,
4809 xfer.amt,
4811 ? std::nullopt
4813 st.spendFee(xfer.from);
4814 st.transfer(xfer.from, srcdoor, xfer.amt);
4815 }
4816
4817 void
4819 {
4820 auto r = bridge_.reward;
4821 auto reward = divide(r, STAmount(bridge_.quorum), r.issue());
4822
4823 for (size_t i = 0; i < num_signers; ++i)
4824 {
4825 if (xfer.attested[i])
4826 st.receive(bridge_.signers[i].account, reward);
4827 }
4829 }
4830
4831 bool
4832 attest(uint64_t time, uint32_t rnd)
4833 {
4834 ChainStateTrack& st = destState();
4835
4836 // check all signers, but start at a random one
4837 for (size_t i = 0; i < num_signers; ++i)
4838 {
4839 size_t signer_idx = (rnd + i) % num_signers;
4840 if (!(xfer.attested[signer_idx]))
4841 {
4842 // enqueue one attestation for this signer
4843 xfer.attested[signer_idx] = true;
4844
4845 st.signers_attns[signer_idx][&bridge_]
4846 .xfer_claims.emplace_back(claim_attestation(
4847 bridge_.signers[signer_idx].account,
4848 bridge_.jvb,
4849 xfer.from,
4850 xfer.amt,
4851 bridge_.signers[signer_idx].account,
4852 xfer.a2b,
4853 xfer.claim_id,
4855 ? std::nullopt
4857 bridge_.signers[signer_idx]));
4858 break;
4859 }
4860 }
4861
4862 // return true if quorum was reached, false otherwise
4863 bool quorum =
4867 {
4870 }
4871 return quorum;
4872 }
4873
4874 void
4876 {
4877 ChainStateTrack& st = destState();
4878 st.env.tx(xchain_claim(
4882 st.spendFee(xfer.to);
4883 }
4884
4885 SmState
4886 advance(uint64_t time, uint32_t rnd)
4887 {
4888 switch (sm_state)
4889 {
4890 case st_initial:
4893 break;
4894
4895 case st_claimid_created:
4896 commit();
4898 break;
4899
4900 case st_attesting:
4901 sm_state = attest(time, rnd)
4903 : st_completed)
4904 : st_attesting;
4905 break;
4906
4907 case st_attested:
4908 assert(xfer.with_claim == WithClaim::yes);
4909 claim();
4911 break;
4912
4913 default:
4914 case st_completed:
4915 assert(0); // should have been removed
4916 break;
4917 }
4918 return sm_state;
4919 }
4920
4921 private:
4924 };
4925
4926 // --------------------------------------------------
4929
4931
4932 void
4934 uint64_t time,
4935 const std::shared_ptr<ChainStateTracker>& chainstate,
4936 BridgeDef const& bridge,
4937 Transfer transfer)
4938 {
4940 time, SmTransfer(chainstate, bridge, std::move(transfer)));
4941 }
4942
4943 void
4944 ac(uint64_t time,
4945 const std::shared_ptr<ChainStateTracker>& chainstate,
4946 BridgeDef const& bridge,
4948 {
4950 time, SmCreateAccount(chainstate, bridge, std::move(ac)));
4951 }
4952
4953public:
4954 void
4957 bool verify_balances = true)
4958 {
4959 using namespace jtx;
4960 uint64_t time = 0;
4961 std::mt19937 gen(27); // Standard mersenne_twister_engine
4963
4964 while (!sm_.empty())
4965 {
4966 ++time;
4967 for (auto it = sm_.begin(); it != sm_.end();)
4968 {
4969 auto vis = [&](auto& sm) {
4970 uint32_t rnd = distrib(gen);
4971 return sm.advance(time, rnd);
4972 };
4973 auto& [t, sm] = *it;
4974 if (t <= time && std::visit(vis, sm) == st_completed)
4975 it = sm_.erase(it);
4976 else
4977 ++it;
4978 }
4979
4980 // send attestations
4981 st->sendAttestations();
4982
4983 // make sure all transactions have been applied
4984 st->a_.env.close();
4985 st->b_.env.close();
4986
4987 if (verify_balances)
4988 {
4989 BEAST_EXPECT(st->verify());
4990 }
4991 }
4992 }
4993
4994 void
4996 {
4997 using namespace jtx;
4998
4999 testcase("Bridge usage simulation");
5000
5001 XEnv mcEnv(*this);
5002 XEnv scEnv(*this, true);
5003
5004 auto st = std::make_shared<ChainStateTracker>(mcEnv, scEnv);
5005
5006 // create 10 accounts + door funded on both chains, and store
5007 // in ChainStateTracker the initial amount of these accounts
5008 Account doorXRPLocking("doorXRPLocking"),
5009 doorUSDLocking("doorUSDLocking"), doorUSDIssuing("doorUSDIssuing");
5010
5011 constexpr size_t num_acct = 10;
5012 auto a = [&doorXRPLocking, &doorUSDLocking, &doorUSDIssuing]() {
5013 using namespace std::literals;
5014 std::vector<Account> result;
5015 result.reserve(num_acct);
5016 for (int i = 0; i < num_acct; ++i)
5017 result.emplace_back(
5018 "a"s + std::to_string(i),
5020 result.emplace_back("doorXRPLocking");
5021 doorXRPLocking = result.back();
5022 result.emplace_back("doorUSDLocking");
5023 doorUSDLocking = result.back();
5024 result.emplace_back("doorUSDIssuing");
5025 doorUSDIssuing = result.back();
5026 return result;
5027 }();
5028
5029 for (auto& acct : a)
5030 {
5031 STAmount amt{XRP(100000)};
5032
5033 mcEnv.fund(amt, acct);
5034 scEnv.fund(amt, acct);
5035 }
5036 Account USDLocking{"USDLocking"};
5037 IOU usdLocking{USDLocking["USD"]};
5038 IOU usdIssuing{doorUSDIssuing["USD"]};
5039
5040 mcEnv.fund(XRP(100000), USDLocking);
5041 mcEnv.close();
5042 mcEnv.tx(trust(doorUSDLocking, usdLocking(100000)));
5043 mcEnv.close();
5044 mcEnv.tx(pay(USDLocking, doorUSDLocking, usdLocking(50000)));
5045
5046 for (int i = 0; i < a.size(); ++i)
5047 {
5048 auto& acct{a[i]};
5049 if (i < num_acct)
5050 {
5051 mcEnv.tx(trust(acct, usdLocking(100000)));
5052 scEnv.tx(trust(acct, usdIssuing(100000)));
5053 }
5054 st->init(acct);
5055 }
5056 for (auto& s : signers)
5057 st->init(s.account);
5058
5059 st->b_.init(Account::master);
5060
5061 // also create some unfunded accounts
5062 constexpr size_t num_ua = 20;
5063 auto ua = []() {
5064 using namespace std::literals;
5065 std::vector<Account> result;
5066 result.reserve(num_ua);
5067 for (int i = 0; i < num_ua; ++i)
5068 result.emplace_back(
5069 "ua"s + std::to_string(i),
5071 return result;
5072 }();
5073
5074 // initialize a bridge from a BridgeDef
5075 auto initBridge = [&mcEnv, &scEnv, &st](BridgeDef& bd) {
5076 bd.initBridge(mcEnv, scEnv);
5077 st->a_.spendFee(bd.doorA, 2);
5078 st->b_.spendFee(bd.doorB, 2);
5079 };
5080
5081 // create XRP -> XRP bridge
5082 // ------------------------
5083 BridgeDef xrp_b{
5084 doorXRPLocking,
5085 xrpIssue(),
5087 xrpIssue(),
5088 XRP(1),
5089 XRP(20),
5090 quorum,
5091 signers,
5093
5094 initBridge(xrp_b);
5095
5096 // create USD -> USD bridge
5097 // ------------------------
5098 BridgeDef usd_b{
5099 doorUSDLocking,
5100 usdLocking,
5101 doorUSDIssuing,
5102 usdIssuing,
5103 XRP(1),
5104 XRP(20),
5105 quorum,
5106 signers,
5108
5109 initBridge(usd_b);
5110
5111 // try a single account create + transfer to validate the simulation
5112 // engine. Do the transfer 8 time steps after the account create, to
5113 // give time enough for ua[0] to be funded now so it can reserve
5114 // the claimID
5115 // -----------------------------------------------------------------
5116 ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, true});
5117 xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), true});
5118 runSimulation(st);
5119
5120 // try the same thing in the other direction
5121 // -----------------------------------------
5122 ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, false});
5123 xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), false});
5124 runSimulation(st);
5125
5126 // run multiple XRP transfers
5127 // --------------------------
5128 xfer(0, st, xrp_b, {a[0], a[0], a[1], XRP(6), true, WithClaim::no});
5129 xfer(1, st, xrp_b, {a[0], a[0], a[1], XRP(8), false, WithClaim::no});
5130 xfer(1, st, xrp_b, {a[1], a[1], a[1], XRP(1), true});
5131 xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(3), false});
5132 xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(5), false});
5133 xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(7), false, WithClaim::no});
5134 xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(9), true});
5135 runSimulation(st);
5136
5137 // run one USD transfer
5138 // --------------------
5139 xfer(0, st, usd_b, {a[0], a[1], a[2], usdLocking(3), true});
5140 runSimulation(st);
5141
5142 // run multiple USD transfers
5143 // --------------------------
5144 xfer(0, st, usd_b, {a[0], a[0], a[1], usdLocking(6), true});
5145 xfer(1, st, usd_b, {a[0], a[0], a[1], usdIssuing(8), false});
5146 xfer(1, st, usd_b, {a[1], a[1], a[1], usdLocking(1), true});
5147 xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(3), false});
5148 xfer(2, st, usd_b, {a[1], a[1], a[1], usdIssuing(5), false});
5149 xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(7), false});
5150 xfer(2, st, usd_b, {a[1], a[1], a[1], usdLocking(9), true});
5151 runSimulation(st);
5152
5153 // run mixed transfers
5154 // -------------------
5155 xfer(0, st, xrp_b, {a[0], a[0], a[0], XRP(1), true});
5156 xfer(0, st, usd_b, {a[1], a[3], a[3], usdIssuing(3), false});
5157 xfer(0, st, usd_b, {a[3], a[2], a[1], usdIssuing(5), false});
5158
5159 xfer(1, st, xrp_b, {a[0], a[0], a[0], XRP(4), false});
5160 xfer(1, st, xrp_b, {a[1], a[1], a[0], XRP(8), true});
5161 xfer(1, st, usd_b, {a[4], a[1], a[1], usdLocking(7), true});
5162
5163 xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(7), true});
5164 xfer(3, st, xrp_b, {a[0], a[4], a[3], XRP(2), false});
5165 xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(9), true});
5166 xfer(3, st, usd_b, {a[3], a[1], a[1], usdIssuing(11), false});
5167 runSimulation(st);
5168
5169 // run multiple account create to stress attestation batching
5170 // ----------------------------------------------------------
5171 ac(0, st, xrp_b, {a[0], ua[1], XRP(301), xrp_b.reward, true});
5172 ac(0, st, xrp_b, {a[1], ua[2], XRP(302), xrp_b.reward, true});
5173 ac(1, st, xrp_b, {a[0], ua[3], XRP(303), xrp_b.reward, true});
5174 ac(2, st, xrp_b, {a[1], ua[4], XRP(304), xrp_b.reward, true});
5175 ac(3, st, xrp_b, {a[0], ua[5], XRP(305), xrp_b.reward, true});
5176 ac(4, st, xrp_b, {a[1], ua[6], XRP(306), xrp_b.reward, true});
5177 ac(6, st, xrp_b, {a[0], ua[7], XRP(307), xrp_b.reward, true});
5178 ac(7, st, xrp_b, {a[2], ua[8], XRP(308), xrp_b.reward, true});
5179 ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true});
5180 ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true});
5181 ac(10, st, xrp_b, {a[0], ua[10], XRP(310), xrp_b.reward, true});
5182 ac(12, st, xrp_b, {a[0], ua[11], XRP(311), xrp_b.reward, true});
5183 ac(12, st, xrp_b, {a[3], ua[12], XRP(312), xrp_b.reward, true});
5184 ac(12, st, xrp_b, {a[4], ua[13], XRP(313), xrp_b.reward, true});
5185 ac(12, st, xrp_b, {a[3], ua[14], XRP(314), xrp_b.reward, true});
5186 ac(12, st, xrp_b, {a[6], ua[15], XRP(315), xrp_b.reward, true});
5187 ac(13, st, xrp_b, {a[7], ua[16], XRP(316), xrp_b.reward, true});
5188 ac(15, st, xrp_b, {a[3], ua[17], XRP(317), xrp_b.reward, true});
5189 runSimulation(st, true); // balances verification working now.
5190 }
5191
5192 void
5193 run() override
5194 {
5196 }
5197};
5198
5199BEAST_DEFINE_TESTSUITE(XChain, app, ripple);
5200BEAST_DEFINE_TESTSUITE(XChainSim, app, ripple);
5201
5202} // namespace ripple::test
T all_of(T... args)
T apply(T... args)
T back(T... args)
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:147
A testsuite class.
Definition: suite.h:53
void pass()
Record a successful test condition.
Definition: suite.h:509
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:153
virtual Config & config()=0
std::unordered_set< uint256, beast::uhash<> > features
Definition: Config.h:284
A currency issued by an account.
Definition: Issue.h:36
Issue const & issue() const
Definition: STAmount.h:487
static constexpr TERSubset fromInt(int from)
Definition: TER.h:411
std::shared_ptr< ChainStateTracker > st_
SmBase(const std::shared_ptr< ChainStateTracker > &chainstate, const BridgeDef &bridge)
SmCreateAccount(const std::shared_ptr< ChainStateTracker > &chainstate, const BridgeDef &bridge, AccountCreate create)
SmState advance(uint64_t time, uint32_t rnd)
void attest(uint64_t time, uint32_t rnd)
void distribute_reward(ChainStateTrack &st)
SmState advance(uint64_t time, uint32_t rnd)
SmTransfer(const std::shared_ptr< ChainStateTracker > &chainstate, const BridgeDef &bridge, Transfer xfer)
bool attest(uint64_t time, uint32_t rnd)
Immutable cryptographic account descriptor.
Definition: Account.h:38
static Account const master
The master account.
Definition: Account.h:47
A transaction testing environment.
Definition: Env.h:117
TER ter() const
Return the TER for the last JTx.
Definition: Env.h:566
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:325
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
void enableFeature(uint256 const feature)
Definition: Env.cpp:584
Application & app()
Definition: Env.h:255
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:237
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:183
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:156
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:225
Converts to IOU Issue or STAmount.
Set the fee on a JTx.
Definition: fee.h:36
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:34
Set the flags on a JTx.
Definition: txflags.h:31
T clear(T... args)
T count(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T front(T... args)
T make_pair(T... args)
@ nullValue
'null' value
Definition: json_value.h:36
Severity
Severity level / threshold of a Journal message.
Definition: Journal.h:31
Keylet xChainClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition: Indexes.cpp:454
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition: Indexes.cpp:441
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition: Indexes.cpp:468
constexpr std::size_t UT_XCHAIN_DEFAULT_QUORUM
Definition: xchain_bridge.h:37
Json::Value create_account_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, jtx::signer const &signer)
JValueVec claim_attestations(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, std::vector< jtx::Account > const &rewardAccounts, bool wasLockingChainSend, std::uint64_t claimID, std::optional< jtx::Account > const &dst, std::vector< jtx::signer > const &signers, std::size_t const numAtts, std::size_t const fromIdx)
Json::Value bridge_create(Account const &acc, Json::Value const &bridge, STAmount const &reward, std::optional< STAmount > const &minAccountCreate)
Json::Value claim_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t claimID, std::optional< jtx::Account > const &dst, jtx::signer const &signer)
Json::Value bridge(Account const &lockingChainDoor, Issue const &lockingChainIssue, Account const &issuingChainDoor, Issue const &issuingChainIssue)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:28
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:35
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:30
std::vector< Json::Value > JValueVec
Definition: xchain_bridge.h:34
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:28
Json::Value sidechain_xchain_account_create(Account const &acc, Json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
Json::Value xchain_claim(Account const &acc, Json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, Account const &dst)
std::unique_ptr< Config > port_increment(std::unique_ptr< Config >, int)
adjust the default configured server ports by a specified value
Definition: envconfig.cpp:127
constexpr std::size_t UT_XCHAIN_DEFAULT_NUM_SIGNERS
Definition: xchain_bridge.h:36
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
Json::Value bridge_modify(Account const &acc, Json::Value const &bridge, std::optional< STAmount > const &reward, std::optional< STAmount > const &minAccountCreate)
Json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount, NetClock::duration const &settleDelay, PublicKey const &pk, std::optional< NetClock::time_point > const &cancelAfter, std::optional< std::uint32_t > const &dstTag)
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
Definition: acctdelete.cpp:29
Json::Value xchain_commit(Account const &acc, Json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, std::optional< Account > const &dst)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
FeatureBitset supported_amendments()
Definition: Env.h:70
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
STAmount divide(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:87
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:84
constexpr std::uint32_t asfRequireDest
Definition: TxFlags.h:76
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:47
constexpr std::uint32_t tfFillOrKill
Definition: TxFlags.h:98
@ current
This was a new validation and was added.
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:79
std::string transToken(TER code)
Definition: TER.cpp:251
constexpr std::uint32_t tfClearAccountCreateAmount
Definition: TxFlags.h:223
@ tecNO_ENTRY
Definition: TER.h:293
@ tecNO_DST
Definition: TER.h:277
@ tecXCHAIN_SELF_COMMIT
Definition: TER.h:337
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition: TER.h:333
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition: TER.h:339
@ tecNO_ISSUER
Definition: TER.h:286
@ tecXCHAIN_NO_CLAIM_ID
Definition: TER.h:324
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition: TER.h:326
@ tecXCHAIN_BAD_CLAIM_ID
Definition: TER.h:325
@ tecDUPLICATE
Definition: TER.h:302
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition: TER.h:334
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecDST_TAG_NEEDED
Definition: TER.h:296
@ tecHAS_OBLIGATIONS
Definition: TER.h:304
@ tecXCHAIN_REWARD_MISMATCH
Definition: TER.h:330
@ tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR
Definition: TER.h:338
@ tecUNFUNDED_PAYMENT
Definition: TER.h:272
@ tecXCHAIN_NO_SIGNERS_LIST
Definition: TER.h:331
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tesSUCCESS
Definition: TER.h:242
@ terNO_LINE
Definition: TER.h:219
@ temBAD_ISSUER
Definition: TER.h:93
@ temBAD_AMOUNT
Definition: TER.h:89
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition: TER.h:135
@ temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
Definition: TER.h:136
@ temXCHAIN_EQUAL_DOOR_ACCOUNTS
Definition: TER.h:131
@ temXCHAIN_BRIDGE_NONDOOR_OWNER
Definition: TER.h:134
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition: TER.h:133
@ temDISABLED
Definition: TER.h:114
STL namespace.
T reserve(T... args)
T size(T... args)
bool payees_received(STAmount const &reward) const
std::vector< balance > reward_accounts
BalanceTransfer(T &env, jtx::Account const &from_acct, jtx::Account const &to_acct, jtx::Account const &payor, jtx::Account const *payees, size_t num_payees, bool withClaim)
bool check_most_balances(STAmount const &amt, STAmount const &reward)
BalanceTransfer(T &env, jtx::Account const &from_acct, jtx::Account const &to_acct, jtx::Account const &payor, std::vector< jtx::Account > const &payees, bool withClaim)
bool has_happened(STAmount const &amt, STAmount const &reward, bool check_payer=true)
Balance(T &env, jtx::Account const &account)
jtx::Account const & account_
STAmount diff() const
void initBridge(ENV &mcEnv, ENV &scEnv)
std::vector< jtx::signer > const & signers
std::shared_ptr< SLE const > bridge(Json::Value const &jvb)
SEnv(T &s, std::unique_ptr< Config > config, FeatureBitset features, std::unique_ptr< Logs > logs=nullptr, beast::severities::Severity thresh=beast::severities::kError)
Definition: XChain_test.cpp:60
SEnv & enableFeature(uint256 const feature)
Definition: XChain_test.cpp:78
std::uint64_t claimCount(Json::Value const &jvb)
std::shared_ptr< SLE const > account(jtx::Account const &account)
SEnv & multiTx(jtx::JValueVec &&jvv, FN const &... fN)
std::shared_ptr< SLE const > caClaimID(Json::Value const &jvb, std::uint64_t seq)
std::uint64_t claimID(Json::Value const &jvb)
std::shared_ptr< SLE const > claimID(Json::Value const &jvb, std::uint64_t seq)
SEnv & tx(JsonValue &&jv, FN const &... fN)
XRPAmount reserve(std::uint32_t count)
STAmount balance(jtx::Account const &account) const
SEnv & disableFeature(uint256 const feature)
Definition: XChain_test.cpp:85
STAmount balance(jtx::Account const &account, Issue const &issue) const
SEnv & fund(STAmount const &amount, Arg const &arg, Args const &... args)
Definition: XChain_test.cpp:93
std::array< bool, num_signers > attested
void init(ENV &env, jtx::Account const &acct)
bool verify(ENV &env, jtx::Account const &acct) const
void spend(jtx::Account const &acct, STAmount amt, std::uint64_t times=1)
void transfer(jtx::Account const &from, jtx::Account const &to, STAmount amt)
uint32_t sendCreateAttestations(size_t signer_idx, BridgeID bridge, CreateClaimVec &claims)
void spendFee(jtx::Account const &acct, size_t times=1)
std::map< BridgeID, BridgeCounters > counters
void sendAttestations(size_t signer_idx, BridgeID bridge, ClaimVec &claims)
void receive(jtx::Account const &acct, STAmount amt, std::uint64_t divisor=1)
std::map< jtx::Account, AccountStateTrack > accounts
std::array< bool, num_signers > attested
void ac(uint64_t time, const std::shared_ptr< ChainStateTracker > &chainstate, BridgeDef const &bridge, AccountCreate ac)
void xfer(uint64_t time, const std::shared_ptr< ChainStateTracker > &chainstate, BridgeDef const &bridge, Transfer transfer)
void run() override
Runs the suite.
static constexpr size_t num_signers
void runSimulation(std::shared_ptr< ChainStateTracker > const &st, bool verify_balances=true)
void run() override
Runs the suite.
void testXChainAddAccountCreateNonBatchAttestation()
XRPAmount reserve(std::uint32_t count)
void testXChainAddClaimNonBatchAttestation()
XEnv(T &s, bool side=false)
STAmount const & value() const
JValueVec att_create_acct_vec(std::uint64_t createCount, jtx::AnyAmount const &amt, jtx::Account const &dst, std::size_t const numAtts, std::size_t const fromIdx=0)
std::vector< signer > const alt_signers
Json::Value create_bridge(Account const &acc, Json::Value const &bridge=Json::nullValue, STAmount const &_reward=XRP(1), std::optional< STAmount > const &minAccountCreate=std::nullopt)
std::vector< Account > const payees
std::vector< signer > const signers
Set the sequence number on a JTx.
Definition: seq.h:34
A signer in a SignerList.
Definition: multisign.h:37
T tmpnam(T... args)
T to_string(T... args)
T visit(T... args)