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) : SEnv<T>(s, jtx::envconfig(), features)
205 {
206 using namespace jtx;
207 STAmount xrp_funds{XRP(10000)};
208
209 if (!side)
210 {
211 this->fund(xrp_funds, mcDoor, mcAlice, mcBob, mcCarol, mcGw);
212
213 // Signer's list must match the attestation signers
214 // env_(jtx::signers(mcDoor, quorum, signers));
215 for (auto& s : signers)
216 this->fund(xrp_funds, s.account);
217 }
218 else
219 {
220 this->fund(
221 xrp_funds,
222 scDoor,
223 scAlice,
224 scBob,
225 scCarol,
226 scGw,
228 scReward);
229
230 for (auto& ra : payees)
231 this->fund(xrp_funds, ra);
232
233 for (auto& s : signers)
234 this->fund(xrp_funds, s.account);
235
236 // Signer's list must match the attestation signers
237 // env_(jtx::signers(Account::master, quorum, signers));
238 }
239 this->close();
240 }
241};
242
243// Tracks the xrp balance for one account
244template <class T>
246{
250
251 Balance(T& env, jtx::Account const& account) : account_(account), env_(env)
252 {
253 startAmount = env_.balance(account_);
254 }
255
257 diff() const
258 {
259 return env_.balance(account_) - startAmount;
260 }
261};
262
263// Tracks the xrp balance for multiple accounts involved in a crosss-chain
264// transfer
265template <class T>
267{
269
272 balance payor_; // pays the rewards
273 std::vector<balance> reward_accounts; // receives the reward
275
277 T& env,
278 jtx::Account const& from_acct,
279 jtx::Account const& to_acct,
280 jtx::Account const& payor,
281 jtx::Account const* payees,
282 size_t num_payees,
283 bool withClaim)
284 : from_(env, from_acct)
285 , to_(env, to_acct)
286 , payor_(env, payor)
287 , reward_accounts([&]() {
289 r.reserve(num_payees);
290 for (size_t i = 0; i < num_payees; ++i)
291 r.emplace_back(env, payees[i]);
292 return r;
293 }())
294 , txFees_(withClaim ? env.env_.current()->fees().base : XRPAmount(0))
295 {
296 }
297
299 T& env,
300 jtx::Account const& from_acct,
301 jtx::Account const& to_acct,
302 jtx::Account const& payor,
303 std::vector<jtx::Account> const& payees,
304 bool withClaim)
306 env,
307 from_acct,
308 to_acct,
309 payor,
310 &payees[0],
311 payees.size(),
312 withClaim)
313 {
314 }
315
316 bool
317 payees_received(STAmount const& reward) const
318 {
319 return std::all_of(
320 reward_accounts.begin(),
321 reward_accounts.end(),
322 [&](const balance& b) { return b.diff() == reward; });
323 }
324
325 bool
326 check_most_balances(STAmount const& amt, STAmount const& reward)
327 {
328 return from_.diff() == -amt && to_.diff() == amt &&
329 payees_received(reward);
330 }
331
332 bool
334 STAmount const& amt,
335 STAmount const& reward,
336 bool check_payer = true)
337 {
338 auto reward_cost =
339 multiply(reward, STAmount(reward_accounts.size()), reward.issue());
340 return check_most_balances(amt, reward) &&
341 (!check_payer || payor_.diff() == -(reward_cost + txFees_));
342 }
343
344 bool
346 {
347 return check_most_balances(STAmount(0), STAmount(0)) &&
348 payor_.diff() <= txFees_; // could have paid fee for failed claim
349 }
350};
351
353{
360 uint32_t quorum;
363
364 template <class ENV>
365 void
366 initBridge(ENV& mcEnv, ENV& scEnv)
367 {
369
370 auto const optAccountCreate = [&]() -> std::optional<STAmount> {
371 if (issueA != xrpIssue() || issueB != xrpIssue())
372 return {};
373 return minAccountCreate;
374 }();
375 mcEnv.tx(bridge_create(doorA, jvb, reward, optAccountCreate))
377 .close();
378
379 scEnv.tx(bridge_create(doorB, jvb, reward, optAccountCreate))
381 .close();
382 }
383};
384
387{
390 {
391 return XEnv(*this).env_.current()->fees().accountReserve(count);
392 }
393
396 {
397 return XEnv(*this).env_.current()->fees().base;
398 }
399
400 void
402 {
403 auto jBridge = create_bridge(mcDoor)[sfXChainBridge.jsonName];
404 bool exceptionPresent = false;
405 try
406 {
407 exceptionPresent = false;
408 [[maybe_unused]] STXChainBridge testBridge1(jBridge);
409 }
410 catch (std::exception& ec)
411 {
412 exceptionPresent = true;
413 }
414
415 BEAST_EXPECT(!exceptionPresent);
416
417 try
418 {
419 exceptionPresent = false;
420 jBridge["Extra"] = 1;
421 [[maybe_unused]] STXChainBridge testBridge2(jBridge);
422 }
423 catch ([[maybe_unused]] std::exception& ec)
424 {
425 exceptionPresent = true;
426 }
427
428 BEAST_EXPECT(exceptionPresent);
429 }
430
431 void
433 {
434 XRPAmount res1 = reserve(1);
435
436 using namespace jtx;
437 testcase("Create Bridge");
438
439 // Normal create_bridge => should succeed
440 XEnv(*this).tx(create_bridge(mcDoor)).close();
441
442 // Bridge not owned by one of the door account.
443 XEnv(*this).tx(
445
446 // Create twice on the same account
447 XEnv(*this)
449 .close()
451
452 // Create USD bridge Alice -> Bob ... should succeed
453 XEnv(*this).tx(
455 mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])),
456 ter(tesSUCCESS));
457
458 // Create USD bridge, Alice is both the locking door and locking issue,
459 // ... should fail.
460 XEnv(*this).tx(
462 mcAlice, bridge(mcAlice, mcAlice["USD"], mcBob, mcBob["USD"])),
464
465 // Bridge where the two door accounts are equal.
466 XEnv(*this).tx(
468 mcBob, bridge(mcBob, mcGw["USD"], mcBob, mcGw["USD"])),
470
471 // Both door accounts are on the same chain. This is not allowed.
472 // Although it doesn't violate any invariants, it's not a useful thing
473 // to do and it complicates the "add claim" transactions.
474 XEnv(*this)
476 mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])))
477 .close()
479 mcBob, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])),
481 .close();
482
483 // Create a bridge on an account with exactly enough balance to
484 // meet the new reserve should succeed
485 XEnv(*this)
486 .fund(res1, mcuDoor) // exact reserve for account + 1 object
487 .close()
489
490 // Create a bridge on an account with no enough balance to meet the
491 // new reserve
492 XEnv(*this)
493 .fund(res1 - 1, mcuDoor) // just short of required reserve
494 .close()
496
497 // Reward amount is non-xrp
498 XEnv(*this).tx(
501
502 // Reward amount is XRP and negative
503 XEnv(*this).tx(
506
507 // Reward amount is 1 xrp => should succeed
509
510 // Min create amount is 1 xrp, mincreate is 1 xrp => should succeed
511 XEnv(*this).tx(
513
514 // Min create amount is non-xrp
515 XEnv(*this).tx(
516 create_bridge(mcDoor, jvb, XRP(1), mcUSD(100)),
518
519 // Min create amount is zero (should fail, currently succeeds)
520 XEnv(*this).tx(
521 create_bridge(mcDoor, jvb, XRP(1), XRP(0)),
523
524 // Min create amount is negative
525 XEnv(*this).tx(
526 create_bridge(mcDoor, jvb, XRP(1), XRP(-1)),
528
529 // coverage test: BridgeCreate::preflight() - create bridge when feature
530 // disabled.
531 {
532 Env env(*this, supported_amendments() - featureXChainBridge);
534 }
535
536 // coverage test: BridgeCreate::preclaim() returns tecNO_ISSUER.
537 XEnv(*this).tx(
539 mcAlice, bridge(mcAlice, mcuAlice["USD"], mcBob, mcBob["USD"])),
541
542 // coverage test: create_bridge transaction with incorrect flag
543 XEnv(*this).tx(
547
548 // coverage test: create_bridge transaction with xchain feature disabled
549 XEnv(*this)
550 .disableFeature(featureXChainBridge)
552 }
553
554 void
556 {
602 using namespace jtx;
603 testcase("Bridge create constraints");
604 XEnv env(*this, true);
605 auto& A = scAlice;
606 auto& B = scBob;
607 auto& C = scCarol;
608 auto AUSD = A["USD"];
609 auto BUSD = B["USD"];
610 auto CUSD = C["USD"];
611 auto GUSD = scGw["USD"];
612 auto AEUR = A["EUR"];
613 auto BEUR = B["EUR"];
614 auto CEUR = C["EUR"];
615 auto GEUR = scGw["EUR"];
616
617 // Accounts to own single brdiges
618 Account const a1("a1");
619 Account const a2("a2");
620 Account const a3("a3");
621 Account const a4("a4");
622 Account const a5("a5");
623 Account const a6("a6");
624
625 env.fund(XRP(10000), a1, a2, a3, a4, a5, a6);
626 env.close();
627
628 // Add a bridge on two different accounts with the same locking and
629 // issuing assets
630 env.tx(create_bridge(a1, bridge(a1, GUSD, B, BUSD))).close();
631 env.tx(create_bridge(a2, bridge(a2, GUSD, B, BUSD))).close();
632
633 // Add the exact same bridge to two different accounts (one locking
634 // account and one issuing)
635 env.tx(create_bridge(a3, bridge(a3, GUSD, a4, a4["USD"]))).close();
636 env.tx(create_bridge(a4, bridge(a3, GUSD, a4, a4["USD"])),
638 .close();
639
640 // Add the exact same bridge to two different accounts (one issuing
641 // account and one locking - opposite order from the test above)
642 env.tx(create_bridge(a5, bridge(a6, GUSD, a5, a5["USD"]))).close();
643 env.tx(create_bridge(a6, bridge(a6, GUSD, a5, a5["USD"])),
645 .close();
646
647 // Test case 1 ~ 5, create bridges
648 auto const goodBridge1 = bridge(A, GUSD, B, BUSD);
649 auto const goodBridge2 = bridge(A, BUSD, C, CUSD);
650 env.tx(create_bridge(B, goodBridge1)).close();
651 // Issuing asset is the same, this is a duplicate
652 env.tx(create_bridge(B, bridge(A, GEUR, B, BUSD)), ter(tecDUPLICATE))
653 .close();
654 env.tx(create_bridge(A, goodBridge2), ter(tesSUCCESS)).close();
655 // Locking asset is the same - this is a duplicate
656 env.tx(create_bridge(A, bridge(A, BUSD, B, BEUR)), ter(tecDUPLICATE))
657 .close();
658 // Locking asset is USD - this is a duplicate even tho it has a
659 // different issuer
660 env.tx(create_bridge(A, bridge(A, CUSD, B, BEUR)), ter(tecDUPLICATE))
661 .close();
662
663 // Test case 6 and 7, commits
664 env.tx(trust(C, BUSD(1000)))
665 .tx(trust(A, BUSD(1000)))
666 .close()
667 .tx(pay(B, C, BUSD(1000)))
668 .close();
669 auto const aBalanceStart = env.balance(A, BUSD);
670 auto const cBalanceStart = env.balance(C, BUSD);
671 env.tx(xchain_commit(C, goodBridge1, 1, BUSD(50))).close();
672 BEAST_EXPECT(env.balance(A, BUSD) - aBalanceStart == BUSD(0));
673 BEAST_EXPECT(env.balance(C, BUSD) - cBalanceStart == BUSD(-50));
674 env.tx(xchain_commit(C, goodBridge2, 1, BUSD(60))).close();
675 BEAST_EXPECT(env.balance(A, BUSD) - aBalanceStart == BUSD(60));
676 BEAST_EXPECT(env.balance(C, BUSD) - cBalanceStart == BUSD(-50 - 60));
677
678 // bridge modify test cases
679 env.tx(bridge_modify(B, goodBridge1, XRP(33), std::nullopt)).close();
680 BEAST_EXPECT((*env.bridge(goodBridge1))[sfSignatureReward] == XRP(33));
681 env.tx(bridge_modify(A, goodBridge2, XRP(44), std::nullopt)).close();
682 BEAST_EXPECT((*env.bridge(goodBridge2))[sfSignatureReward] == XRP(44));
683 }
684
685 void
687 {
688 using namespace jtx;
689 testcase("Create Bridge Matrix");
690
691 // Test all combinations of the following:`
692 // --------------------------------------
693 // - Locking chain is IOU with locking chain door account as issuer
694 // - Locking chain is IOU with issuing chain door account that
695 // exists on the locking chain as issuer
696 // - Locking chain is IOU with issuing chain door account that does
697 // not exists on the locking chain as issuer
698 // - Locking chain is IOU with non-door account (that exists on the
699 // locking chain ledger) as issuer
700 // - Locking chain is IOU with non-door account (that does not exist
701 // exists on the locking chain ledger) as issuer
702 // - Locking chain is XRP
703 // ---------------------------------------------------------------------
704 // - Issuing chain is IOU with issuing chain door account as the
705 // issuer
706 // - Issuing chain is IOU with locking chain door account (that
707 // exists on the issuing chain ledger) as the issuer
708 // - Issuing chain is IOU with locking chain door account (that does
709 // not exist on the issuing chain ledger) as the issuer
710 // - Issuing chain is IOU with non-door account (that exists on the
711 // issuing chain ledger) as the issuer
712 // - Issuing chain is IOU with non-door account (that does not
713 // exists on the issuing chain ledger) as the issuer
714 // - Issuing chain is XRP and issuing chain door account is not the
715 // root account
716 // - Issuing chain is XRP and issuing chain door account is the root
717 // account
718 // ---------------------------------------------------------------------
719 // That's 42 combinations. The only combinations that should succeed
720 // are:
721 // - Locking chain is any IOU,
722 // - Issuing chain is IOU with issuing chain door account as the
723 // issuer
724 // Locking chain is XRP,
725 // - Issuing chain is XRP with issuing chain is the root account.
726 // ---------------------------------------------------------------------
727 Account a("a"), b("b");
728 Issue ia, ib;
729
730 std::tuple lcs{
732 "Locking chain is IOU(locking chain door)",
733 [&](auto& env, bool) {
734 a = mcDoor;
735 ia = mcDoor["USD"];
736 }),
738 "Locking chain is IOU(issuing chain door funded on locking "
739 "chain)",
740 [&](auto& env, bool shouldFund) {
741 a = mcDoor;
742 ia = scDoor["USD"];
743 if (shouldFund)
744 env.fund(XRP(10000), scDoor);
745 }),
747 "Locking chain is IOU(issuing chain door account unfunded "
748 "on locking chain)",
749 [&](auto& env, bool) {
750 a = mcDoor;
751 ia = scDoor["USD"];
752 }),
754 "Locking chain is IOU(bob funded on locking chain)",
755 [&](auto& env, bool) {
756 a = mcDoor;
757 ia = mcGw["USD"];
758 }),
760 "Locking chain is IOU(bob unfunded on locking chain)",
761 [&](auto& env, bool) {
762 a = mcDoor;
763 ia = mcuGw["USD"];
764 }),
765 std::make_pair("Locking chain is XRP", [&](auto& env, bool) {
766 a = mcDoor;
767 ia = xrpIssue();
768 })};
769
770 std::tuple ics{
772 "Issuing chain is IOU(issuing chain door account)",
773 [&](auto& env, bool) {
774 b = scDoor;
775 ib = scDoor["USD"];
776 }),
778 "Issuing chain is IOU(locking chain door funded on issuing "
779 "chain)",
780 [&](auto& env, bool shouldFund) {
781 b = scDoor;
782 ib = mcDoor["USD"];
783 if (shouldFund)
784 env.fund(XRP(10000), mcDoor);
785 }),
787 "Issuing chain is IOU(locking chain door unfunded on "
788 "issuing chain)",
789 [&](auto& env, bool) {
790 b = scDoor;
791 ib = mcDoor["USD"];
792 }),
794 "Issuing chain is IOU(bob funded on issuing chain)",
795 [&](auto& env, bool) {
796 b = scDoor;
797 ib = mcGw["USD"];
798 }),
800 "Issuing chain is IOU(bob unfunded on issuing chain)",
801 [&](auto& env, bool) {
802 b = scDoor;
803 ib = mcuGw["USD"];
804 }),
806 "Issuing chain is XRP and issuing chain door account is "
807 "not the root account",
808 [&](auto& env, bool) {
809 b = scDoor;
810 ib = xrpIssue();
811 }),
813 "Issuing chain is XRP and issuing chain door account is "
814 "the root account ",
815 [&](auto& env, bool) {
816 b = Account::master;
817 ib = xrpIssue();
818 })};
819
820 std::vector<std::pair<int, int>> expected_result{
863
865
866 auto testcase = [&](auto const& lc, auto const& ic) {
867 XEnv mcEnv(*this);
868 XEnv scEnv(*this, true);
869
870 lc.second(mcEnv, true);
871 lc.second(scEnv, false);
872
873 ic.second(mcEnv, false);
874 ic.second(scEnv, true);
875
876 auto const& expected = expected_result[test_result.size()];
877
878 mcEnv.tx(
879 create_bridge(a, bridge(a, ia, b, ib)),
880 ter(TER::fromInt(expected.first)));
881 TER mcTER = mcEnv.env_.ter();
882
883 scEnv.tx(
884 create_bridge(b, bridge(a, ia, b, ib)),
885 ter(TER::fromInt(expected.second)));
886 TER scTER = scEnv.env_.ter();
887
888 bool pass = mcTER == tesSUCCESS && scTER == tesSUCCESS;
889
890 test_result.emplace_back(mcTER, scTER, pass);
891 };
892
893 auto apply_ics = [&](auto const& lc, auto const& ics) {
895 [&](auto const&... ic) { (testcase(lc, ic), ...); }, ics);
896 };
897
898 std::apply([&](auto const&... lc) { (apply_ics(lc, ics), ...); }, lcs);
899
900#if GENERATE_MTX_OUTPUT
901 // optional output of matrix results in markdown format
902 // ----------------------------------------------------
903 std::string fname{std::tmpnam(nullptr)};
904 fname += ".md";
905 std::cout << "Markdown output for matrix test: " << fname << "\n";
906
907 auto print_res = [](auto tup) -> std::string {
908 std::string status = std::string(transToken(std::get<0>(tup))) +
909 " / " + transToken(std::get<1>(tup));
910
911 if (std::get<2>(tup))
912 return status;
913 else
914 {
915 // red
916 return std::string("`") + status + "`";
917 }
918 };
919
920 auto output_table = [&](auto print_res) {
921 size_t test_idx = 0;
922 std::string res;
923 res.reserve(10000); // should be enough :-)
924
925 // first two header lines
926 res += "| `issuing ->` | ";
928 [&](auto const&... ic) {
929 ((res += ic.first, res += " | "), ...);
930 },
931 ics);
932 res += "\n";
933
934 res += "| :--- | ";
936 [&](auto const&... ic) {
937 (((void)ic.first, res += ":---: | "), ...);
938 },
939 ics);
940 res += "\n";
941
942 auto output = [&](auto const& lc, auto const& ic) {
943 res += print_res(test_result[test_idx]);
944 res += " | ";
945 ++test_idx;
946 };
947
948 auto output_ics = [&](auto const& lc, auto const& ics) {
949 res += "| ";
950 res += lc.first;
951 res += " | ";
953 [&](auto const&... ic) { (output(lc, ic), ...); }, ics);
954 res += "\n";
955 };
956
958 [&](auto const&... lc) { (output_ics(lc, ics), ...); }, lcs);
959
960 return res;
961 };
962
963 std::ofstream(fname) << output_table(print_res);
964
965 std::string ter_fname{std::tmpnam(nullptr)};
966 std::cout << "ter output for matrix test: " << ter_fname << "\n";
967
968 std::ofstream ofs(ter_fname);
969 for (auto& t : test_result)
970 {
971 ofs << "{ " << std::string(transToken(std::get<0>(t))) << ", "
972 << std::string(transToken(std::get<1>(t))) << "}\n,";
973 }
974#endif
975 }
976
977 void
979 {
980 using namespace jtx;
981 testcase("Modify Bridge");
982
983 // Changing a non-existent bridge should fail
984 XEnv(*this).tx(
986 mcAlice,
987 bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"]),
988 XRP(2),
989 std::nullopt),
991
992 // must change something
993 // XEnv(*this)
994 // .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1)))
995 // .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(1)),
996 // ter(temMALFORMED));
997
998 // must change something
999 XEnv(*this)
1000 .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1)))
1001 .close()
1002 .tx(bridge_modify(mcDoor, jvb, {}, {}), ter(temMALFORMED));
1003
1004 // Reward amount is non-xrp
1005 XEnv(*this).tx(
1006 bridge_modify(mcDoor, jvb, mcUSD(2), XRP(10)),
1008
1009 // Reward amount is XRP and negative
1010 XEnv(*this).tx(
1011 bridge_modify(mcDoor, jvb, XRP(-2), XRP(10)),
1013
1014 // Min create amount is non-xrp
1015 XEnv(*this).tx(
1016 bridge_modify(mcDoor, jvb, XRP(2), mcUSD(10)),
1018
1019 // Min create amount is zero
1020 XEnv(*this).tx(
1021 bridge_modify(mcDoor, jvb, XRP(2), XRP(0)),
1023
1024 // Min create amount is negative
1025 XEnv(*this).tx(
1026 bridge_modify(mcDoor, jvb, XRP(2), XRP(-10)),
1028
1029 // First check the regular claim process (without bridge_modify)
1030 for (auto withClaim : {false, true})
1031 {
1032 XEnv mcEnv(*this);
1033 XEnv scEnv(*this, true);
1034
1035 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1036
1039 .close()
1041 .close();
1042
1043 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1044 auto const amt = XRP(1000);
1045 std::uint32_t const claimID = 1;
1046 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1047
1048 BalanceTransfer transfer(
1049 scEnv,
1051 scBob,
1052 scAlice,
1053 &payees[0],
1055 withClaim);
1056
1057 scEnv
1059 scAttester,
1060 jvb,
1061 mcAlice,
1062 amt,
1063 payees,
1064 true,
1065 claimID,
1066 dst,
1067 signers))
1068 .close();
1069
1070 if (withClaim)
1071 {
1072 BEAST_EXPECT(transfer.has_not_happened());
1073
1074 // need to submit a claim transactions
1075 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1076 .close();
1077 }
1078
1079 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
1080 }
1081
1082 // Check that the reward paid from a claim Id was the reward when
1083 // the claim id was created, not the reward since the bridge was
1084 // modified.
1085 for (auto withClaim : {false, true})
1086 {
1087 XEnv mcEnv(*this);
1088 XEnv scEnv(*this, true);
1089
1090 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1091
1094 .close()
1096 .close();
1097
1098 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1099 auto const amt = XRP(1000);
1100 std::uint32_t const claimID = 1;
1101 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1102
1103 // Now modify the reward on the bridge
1104 mcEnv.tx(bridge_modify(mcDoor, jvb, XRP(2), XRP(10))).close();
1105 scEnv.tx(bridge_modify(Account::master, jvb, XRP(2), XRP(10)))
1106 .close();
1107
1108 BalanceTransfer transfer(
1109 scEnv,
1111 scBob,
1112 scAlice,
1113 &payees[0],
1115 withClaim);
1116
1117 scEnv
1119 scAttester,
1120 jvb,
1121 mcAlice,
1122 amt,
1123 payees,
1124 true,
1125 claimID,
1126 dst,
1127 signers))
1128 .close();
1129
1130 if (withClaim)
1131 {
1132 BEAST_EXPECT(transfer.has_not_happened());
1133
1134 // need to submit a claim transactions
1135 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1136 .close();
1137 }
1138
1139 // make sure the reward accounts indeed received the original
1140 // split reward (1 split 5 ways) instead of the updated 2 XRP.
1141 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
1142 }
1143
1144 // Check that the signatures used to verify attestations and decide
1145 // if there is a quorum are the current signer's list on the door
1146 // account, not the signer's list that was in effect when the claim
1147 // id was created.
1148 for (auto withClaim : {false, true})
1149 {
1150 XEnv mcEnv(*this);
1151 XEnv scEnv(*this, true);
1152
1153 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1154
1157 .close()
1159 .close();
1160
1161 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1162 auto const amt = XRP(1000);
1163 std::uint32_t const claimID = 1;
1164 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1165
1166 // change signers - claim should not be processed is the batch
1167 // is signed by original signers
1169 .close();
1170
1171 BalanceTransfer transfer(
1172 scEnv,
1174 scBob,
1175 scAlice,
1176 &payees[0],
1178 withClaim);
1179
1180 // submit claim using outdated signers - should fail
1181 scEnv
1182 .multiTx(
1184 scAttester,
1185 jvb,
1186 mcAlice,
1187 amt,
1188 payees,
1189 true,
1190 claimID,
1191 dst,
1192 signers),
1194 .close();
1195 if (withClaim)
1196 {
1197 // need to submit a claim transactions
1198 scEnv
1199 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
1201 .close();
1202 }
1203
1204 // make sure transfer has not happened as we sent attestations
1205 // using outdated signers
1206 BEAST_EXPECT(transfer.has_not_happened());
1207
1208 // submit claim using current signers - should succeed
1209 scEnv
1211 scAttester,
1212 jvb,
1213 mcAlice,
1214 amt,
1215 payees,
1216 true,
1217 claimID,
1218 dst,
1219 alt_signers))
1220 .close();
1221 if (withClaim)
1222 {
1223 BEAST_EXPECT(transfer.has_not_happened());
1224
1225 // need to submit a claim transactions
1226 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1227 .close();
1228 }
1229
1230 // make sure the transfer went through as we sent attestations
1231 // using new signers
1232 BEAST_EXPECT(
1233 transfer.has_happened(amt, split_reward_quorum, false));
1234 }
1235
1236 // coverage test: bridge_modify transaction with incorrect flag
1237 XEnv(*this)
1239 .close()
1240 .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(2)),
1243
1244 // coverage test: bridge_modify transaction with xchain feature
1245 // disabled
1246 XEnv(*this)
1248 .disableFeature(featureXChainBridge)
1249 .close()
1251
1252 // coverage test: bridge_modify return temSIDECHAIN_NONDOOR_OWNER;
1253 XEnv(*this)
1255 .close()
1256 .tx(bridge_modify(mcAlice, jvb, XRP(1), XRP(2)),
1258
1265 XEnv(*this)
1266 .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20)))
1267 .close()
1269 mcAlice, jvb, scuAlice, XRP(100), reward))
1270 .close()
1271 .tx(bridge_modify(mcDoor, jvb, {}, XRP(2)),
1274 .close()
1275 .tx(bridge_modify(mcDoor, jvb, XRP(3), {}),
1277 .close()
1279 mcAlice, jvb, scuBob, XRP(100), XRP(3)),
1281 .close();
1282 }
1283
1284 void
1286 {
1287 using namespace jtx;
1288 XRPAmount res1 = reserve(1);
1289 XRPAmount tx_fee = txFee();
1290
1291 testcase("Create ClaimID");
1292
1293 // normal bridge create for sanity check with the exact necessary
1294 // account balance
1295 XEnv(*this, true)
1297 .fund(res1, scuAlice) // acct reserve + 1 object
1298 .close()
1300 .close();
1301
1302 // check reward not deducted when claim id is created
1303 {
1304 XEnv xenv(*this, true);
1305
1306 Balance scAlice_bal(xenv, scAlice);
1307
1310 .close();
1311
1312 BEAST_EXPECT(scAlice_bal.diff() == -tx_fee);
1313 }
1314
1315 // Non-existent bridge
1316 XEnv(*this, true)
1318 scAlice,
1319 bridge(mcAlice, mcAlice["USD"], scBob, scBob["USD"]),
1320 reward,
1321 mcAlice),
1323 .close();
1324
1325 // Creating the new object would put the account below the reserve
1326 XEnv(*this, true)
1328 .fund(res1 - xrp_dust, scuAlice) // barely not enough
1329 .close()
1332 .close();
1333
1334 // The specified reward doesn't match the reward on the bridge (test
1335 // by giving the reward amount for the other side, as well as a
1336 // completely non-matching reward)
1337 XEnv(*this, true)
1339 .close()
1343 .close();
1344
1345 // A reward amount that isn't XRP
1346 XEnv(*this, true)
1348 .close()
1351 .close();
1352
1353 // coverage test: xchain_create_claim_id transaction with incorrect
1354 // flag
1355 XEnv(*this, true)
1357 .close()
1361 .close();
1362
1363 // coverage test: xchain_create_claim_id transaction with xchain
1364 // feature disabled
1365 XEnv(*this, true)
1367 .disableFeature(featureXChainBridge)
1368 .close()
1371 .close();
1372 }
1373
1374 void
1376 {
1377 using namespace jtx;
1378 XRPAmount res0 = reserve(0);
1379 XRPAmount tx_fee = txFee();
1380
1381 testcase("Commit");
1382
1383 // Commit to a non-existent bridge
1384 XEnv(*this).tx(
1386
1387 // check that reward not deducted when doing the commit
1388 {
1389 XEnv xenv(*this);
1390
1391 Balance alice_bal(xenv, mcAlice);
1392 auto const amt = XRP(1000);
1393
1394 xenv.tx(create_bridge(mcDoor, jvb))
1395 .close()
1396 .tx(xchain_commit(mcAlice, jvb, 1, amt, scBob))
1397 .close();
1398
1399 STAmount claim_cost = amt;
1400 BEAST_EXPECT(alice_bal.diff() == -(claim_cost + tx_fee));
1401 }
1402
1403 // Commit a negative amount
1404 XEnv(*this)
1406 .close()
1407 .tx(xchain_commit(mcAlice, jvb, 1, XRP(-1), scBob),
1409
1410 // Commit an amount whose issue that does not match the expected
1411 // issue on the bridge (either LockingChainIssue or
1412 // IssuingChainIssue, depending on the chain).
1413 XEnv(*this)
1415 .close()
1416 .tx(xchain_commit(mcAlice, jvb, 1, mcUSD(100), scBob),
1418
1419 // Commit an amount that would put the sender below the required
1420 // reserve (if XRP)
1421 XEnv(*this)
1423 .fund(res0 + one_xrp - xrp_dust, mcuAlice) // barely not enough
1424 .close()
1427
1428 XEnv(*this)
1430 .fund(
1431 res0 + one_xrp + xrp_dust, // "xrp_dust" for tx fees
1432 mcuAlice) // exactly enough => should succeed
1433 .close()
1435
1436 // Commit an amount above the account's balance (for both XRP and
1437 // IOUs)
1438 XEnv(*this)
1440 .fund(res0, mcuAlice) // barely not enough
1441 .close()
1442 .tx(xchain_commit(mcuAlice, jvb, 1, res0 + one_xrp, scBob),
1444
1445 auto jvb_USD = bridge(mcDoor, mcUSD, scGw, scUSD);
1446
1447 // commit sent from iou issuer (mcGw) succeeds - should it?
1448 XEnv(*this)
1449 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1450 .tx(create_bridge(mcDoor, jvb_USD))
1451 .close()
1452 .tx(xchain_commit(mcGw, jvb_USD, 1, mcUSD(1), scBob));
1453
1454 // commit to a door account from the door account. This should fail.
1455 XEnv(*this)
1456 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1457 .tx(create_bridge(mcDoor, jvb_USD))
1458 .close()
1459 .tx(xchain_commit(mcDoor, jvb_USD, 1, mcUSD(1), scBob),
1461
1462 // commit sent from mcAlice which has no IOU balance => should fail
1463 XEnv(*this)
1464 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1465 .tx(create_bridge(mcDoor, jvb_USD))
1466 .close()
1467 .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(1), scBob),
1468 ter(terNO_LINE));
1469
1470 // commit sent from mcAlice which has no IOU balance => should fail
1471 // just changed the destination to scGw (which is the door account
1472 // and may not make much sense)
1473 XEnv(*this)
1474 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1475 .tx(create_bridge(mcDoor, jvb_USD))
1476 .close()
1477 .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(1), scGw),
1478 ter(terNO_LINE));
1479
1480 // commit sent from mcAlice which has a IOU balance => should
1481 // succeed
1482 XEnv(*this)
1483 .tx(trust(mcDoor, mcUSD(10000)))
1484 .tx(trust(mcAlice, mcUSD(10000)))
1485 .close()
1486 .tx(pay(mcGw, mcAlice, mcUSD(10)))
1487 .tx(create_bridge(mcDoor, jvb_USD))
1488 .close()
1489 //.tx(pay(mcAlice, mcDoor, mcUSD(10)));
1490 .tx(xchain_commit(mcAlice, jvb_USD, 1, mcUSD(10), scAlice));
1491
1492 // coverage test: xchain_commit transaction with incorrect flag
1493 XEnv(*this)
1495 .close()
1499
1500 // coverage test: xchain_commit transaction with xchain feature
1501 // disabled
1502 XEnv(*this)
1504 .disableFeature(featureXChainBridge)
1505 .close()
1507 ter(temDISABLED));
1508 }
1509
1510 void
1512 {
1513 using namespace jtx;
1514
1515 testcase("Add Attestation");
1516 XRPAmount res0 = reserve(0);
1517 XRPAmount tx_fee = txFee();
1518
1519 auto multiTtxFee = [&](std::uint32_t m) -> STAmount {
1520 return multiply(tx_fee, STAmount(m), xrpIssue());
1521 };
1522
1523 // Add an attestation to a claim id that has already reached quorum.
1524 // This should succeed and share in the reward.
1525 // note: this is true only when either:
1526 // 1. dest account is not specified, so transfer requires a claim
1527 // 2. or the extra attestation is sent in the same batch as the
1528 // one reaching quorum
1529 for (auto withClaim : {true})
1530 {
1531 XEnv mcEnv(*this);
1532 XEnv scEnv(*this, true);
1533 std::uint32_t const claimID = 1;
1534
1535 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1536
1539 .close()
1541 .close();
1542
1543 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1544
1545 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1546 auto const amt = XRP(1000);
1547 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1548
1549 BalanceTransfer transfer(
1550 scEnv, Account::master, scBob, scAlice, payees, withClaim);
1551
1552 scEnv
1554 scAttester,
1555 jvb,
1556 mcAlice,
1557 amt,
1558 payees,
1559 true,
1560 claimID,
1561 dst,
1562 signers,
1564 .close();
1565 scEnv
1567 scAttester,
1568 jvb,
1569 mcAlice,
1570 amt,
1572 true,
1573 claimID,
1574 dst,
1576 .close();
1577
1578 if (withClaim)
1579 {
1580 BEAST_EXPECT(transfer.has_not_happened());
1581
1582 // need to submit a claim transactions
1583 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1584 .close();
1585 BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted
1586 BEAST_EXPECT(scEnv.claimID(jvb) == claimID);
1587 }
1588
1589 BEAST_EXPECT(transfer.has_happened(amt, split_reward_everyone));
1590 }
1591
1592 // Test that signature weights are correctly handled. Assign
1593 // signature weights of 1,2,4,4 and a quorum of 7. Check that the
1594 // 4,4 signatures reach a quorum, the 1,2,4, reach a quorum, but the
1595 // 4,2, 4,1 and 1,2 do not.
1596
1597 // 1,2,4 => should succeed
1598 for (auto withClaim : {false, true})
1599 {
1600 XEnv mcEnv(*this);
1601 XEnv scEnv(*this, true);
1602
1603 std::uint32_t const quorum_7 = 7;
1604 std::vector<signer> const signers_ = [] {
1605 constexpr int numSigners = 4;
1606 std::uint32_t weights[] = {1, 2, 4, 4};
1607
1608 std::vector<signer> result;
1609 result.reserve(numSigners);
1610 for (int i = 0; i < numSigners; ++i)
1611 {
1612 using namespace std::literals;
1613 auto const a = Account("signer_"s + std::to_string(i));
1614 result.emplace_back(a, weights[i]);
1615 }
1616 return result;
1617 }();
1618
1619 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1620
1622 .tx(jtx::signers(Account::master, quorum_7, signers_))
1623 .close()
1625 .close();
1626 std::uint32_t const claimID = 1;
1627 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1628
1629 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1630 auto const amt = XRP(1000);
1631
1632 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1633
1634 BalanceTransfer transfer(
1635 scEnv,
1637 scBob,
1638 scAlice,
1639 &payees[0],
1640 3,
1641 withClaim);
1642
1643 scEnv
1645 scAttester,
1646 jvb,
1647 mcAlice,
1648 amt,
1649 payees,
1650 true,
1651 claimID,
1652 dst,
1653 signers_,
1654 3))
1655 .close();
1656
1657 if (withClaim)
1658 {
1659 BEAST_EXPECT(transfer.has_not_happened());
1660
1661 // need to submit a claim transactions
1662 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1663 .close();
1664 }
1665
1666 BEAST_EXPECT(!scEnv.claimID(jvb, 1)); // claim id deleted
1667
1668 BEAST_EXPECT(transfer.has_happened(
1669 amt, divide(reward, STAmount(3), reward.issue())));
1670 }
1671
1672 // 4,4 => should succeed
1673 for (auto withClaim : {false, true})
1674 {
1675 XEnv mcEnv(*this);
1676 XEnv scEnv(*this, true);
1677
1678 std::uint32_t const quorum_7 = 7;
1679 std::vector<signer> const signers_ = [] {
1680 constexpr int numSigners = 4;
1681 std::uint32_t weights[] = {1, 2, 4, 4};
1682
1683 std::vector<signer> result;
1684 result.reserve(numSigners);
1685 for (int i = 0; i < numSigners; ++i)
1686 {
1687 using namespace std::literals;
1688 auto const a = Account("signer_"s + std::to_string(i));
1689 result.emplace_back(a, weights[i]);
1690 }
1691 return result;
1692 }();
1693 STAmount const split_reward_ =
1694 divide(reward, STAmount(signers_.size()), reward.issue());
1695
1696 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1697
1699 .tx(jtx::signers(Account::master, quorum_7, signers_))
1700 .close()
1702 .close();
1703 std::uint32_t const claimID = 1;
1704 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1705
1706 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1707 auto const amt = XRP(1000);
1708
1709 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1710
1711 BalanceTransfer transfer(
1712 scEnv,
1714 scBob,
1715 scAlice,
1716 &payees[2],
1717 2,
1718 withClaim);
1719
1720 scEnv
1722 scAttester,
1723 jvb,
1724 mcAlice,
1725 amt,
1726 payees,
1727 true,
1728 claimID,
1729 dst,
1730 signers_,
1731 2,
1732 2))
1733 .close();
1734
1735 if (withClaim)
1736 {
1737 BEAST_EXPECT(transfer.has_not_happened());
1738
1739 // need to submit a claim transactions
1740 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
1741 .close();
1742 }
1743
1744 BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted
1745
1746 BEAST_EXPECT(transfer.has_happened(
1747 amt, divide(reward, STAmount(2), reward.issue())));
1748 }
1749
1750 // 1,2 => should fail
1751 for (auto withClaim : {false, true})
1752 {
1753 XEnv mcEnv(*this);
1754 XEnv scEnv(*this, true);
1755
1756 std::uint32_t const quorum_7 = 7;
1757 std::vector<signer> const signers_ = [] {
1758 constexpr int numSigners = 4;
1759 std::uint32_t weights[] = {1, 2, 4, 4};
1760
1761 std::vector<signer> result;
1762 result.reserve(numSigners);
1763 for (int i = 0; i < numSigners; ++i)
1764 {
1765 using namespace std::literals;
1766 auto const a = Account("signer_"s + std::to_string(i));
1767 result.emplace_back(a, weights[i]);
1768 }
1769 return result;
1770 }();
1771
1772 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1773
1775 .tx(jtx::signers(Account::master, quorum_7, signers_))
1776 .close()
1778 .close();
1779
1780 std::uint32_t const claimID = 1;
1781 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1782
1783 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1784 auto const amt = XRP(1000);
1785 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1786
1787 BalanceTransfer transfer(
1788 scEnv,
1790 scBob,
1791 scAlice,
1792 &payees[0],
1793 2,
1794 withClaim);
1795
1796 scEnv
1798 scAttester,
1799 jvb,
1800 mcAlice,
1801 amt,
1802 payees,
1803 true,
1804 claimID,
1805 dst,
1806 signers_,
1807 2))
1808 .close();
1809 if (withClaim)
1810 {
1811 BEAST_EXPECT(transfer.has_not_happened());
1812
1813 // need to submit a claim transactions
1814 scEnv
1815 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
1817 .close();
1818 }
1819
1820 BEAST_EXPECT(
1821 !!scEnv.claimID(jvb, claimID)); // claim id still present
1822 BEAST_EXPECT(transfer.has_not_happened());
1823 }
1824
1825 // 2,4 => should fail
1826 for (auto withClaim : {false, true})
1827 {
1828 XEnv mcEnv(*this);
1829 XEnv scEnv(*this, true);
1830
1831 std::uint32_t const quorum_7 = 7;
1832 std::vector<signer> const signers_ = [] {
1833 constexpr int numSigners = 4;
1834 std::uint32_t weights[] = {1, 2, 4, 4};
1835
1836 std::vector<signer> result;
1837 result.reserve(numSigners);
1838 for (int i = 0; i < numSigners; ++i)
1839 {
1840 using namespace std::literals;
1841 auto const a = Account("signer_"s + std::to_string(i));
1842 result.emplace_back(a, weights[i]);
1843 }
1844 return result;
1845 }();
1846
1847 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
1848
1850 .tx(jtx::signers(Account::master, quorum_7, signers_))
1851 .close()
1853 .close();
1854
1855 std::uint32_t const claimID = 1;
1856 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1857
1858 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1859 auto const amt = XRP(1000);
1860
1861 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
1862
1863 BalanceTransfer transfer(
1864 scEnv,
1866 scBob,
1867 scAlice,
1868 &payees[1],
1869 2,
1870 withClaim);
1871
1872 scEnv
1874 scAttester,
1875 jvb,
1876 mcAlice,
1877 amt,
1878 payees,
1879 true,
1880 claimID,
1881 dst,
1882 signers_,
1883 2,
1884 1))
1885 .close();
1886
1887 if (withClaim)
1888 {
1889 BEAST_EXPECT(transfer.has_not_happened());
1890
1891 // need to submit a claim transactions
1892 scEnv
1893 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
1895 .close();
1896 }
1897
1898 BEAST_EXPECT(
1899 !!scEnv.claimID(jvb, claimID)); // claim id still present
1900 BEAST_EXPECT(transfer.has_not_happened());
1901 }
1902
1903 // Confirm that account create transactions happen in the correct
1904 // order. If they reach quorum out of order they should not execute
1905 // until all the previous created transactions have occurred.
1906 // Re-adding an attestation should move funds.
1907 {
1908 XEnv mcEnv(*this);
1909 XEnv scEnv(*this, true);
1910 auto const amt = XRP(1000);
1911 auto const amt_plus_reward = amt + reward;
1912
1913 {
1914 Balance door(mcEnv, mcDoor);
1915 Balance carol(mcEnv, mcCarol);
1916
1917 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20)))
1918 .close()
1920 mcAlice, jvb, scuAlice, amt, reward))
1922 mcBob, jvb, scuBob, amt, reward))
1924 mcCarol, jvb, scuCarol, amt, reward))
1925 .close();
1926
1927 BEAST_EXPECT(
1928 door.diff() ==
1929 (multiply(amt_plus_reward, STAmount(3), xrpIssue()) -
1930 tx_fee));
1931 BEAST_EXPECT(carol.diff() == -(amt + reward + tx_fee));
1932 }
1933
1936 .close();
1937
1938 {
1939 // send first batch of account create attest for all 3
1940 // account create
1941 Balance attester(scEnv, scAttester);
1942 Balance door(scEnv, Account::master);
1943
1944 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2))
1946 .multiTx(att_create_acct_vec(2, amt, scuBob, 2))
1947 .close();
1948
1949 BEAST_EXPECT(door.diff() == STAmount(0));
1950 // att_create_acct_vec return vectors of size 2, so 2*3 txns
1951 BEAST_EXPECT(attester.diff() == -multiTtxFee(6));
1952
1953 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // ca claim id present
1954 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present
1955 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present
1956 BEAST_EXPECT(
1957 scEnv.claimCount(jvb) == 0); // claim count still 0
1958 }
1959
1960 {
1961 // complete attestations for 2nd account create => should
1962 // not complete
1963 Balance attester(scEnv, scAttester);
1964 Balance door(scEnv, Account::master);
1965
1966 scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 3, 2))
1967 .close();
1968
1969 BEAST_EXPECT(door.diff() == STAmount(0));
1970 // att_create_acct_vec return vectors of size 3, so 3 txns
1971 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1972
1973 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present
1974 BEAST_EXPECT(
1975 scEnv.claimCount(jvb) == 0); // claim count still 0
1976 }
1977
1978 {
1979 // complete attestations for 3rd account create => should
1980 // not complete
1981 Balance attester(scEnv, scAttester);
1982 Balance door(scEnv, Account::master);
1983
1984 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 3, 2))
1985 .close();
1986
1987 BEAST_EXPECT(door.diff() == STAmount(0));
1988 // att_create_acct_vec return vectors of size 3, so 3 txns
1989 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1990
1991 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present
1992 BEAST_EXPECT(
1993 scEnv.claimCount(jvb) == 0); // claim count still 0
1994 }
1995
1996 {
1997 // complete attestations for 1st account create => account
1998 // should be created
1999 Balance attester(scEnv, scAttester);
2000 Balance door(scEnv, Account::master);
2001
2002 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 3, 1))
2003 .close();
2004
2005 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2006 // att_create_acct_vec return vectors of size 3, so 3 txns
2007 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
2008 BEAST_EXPECT(scEnv.balance(scuAlice) == amt);
2009
2010 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id 1 deleted
2011 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present
2012 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
2013 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count now 1
2014 }
2015
2016 {
2017 // resend attestations for 3rd account create => still
2018 // should not complete
2019 Balance attester(scEnv, scAttester);
2020 Balance door(scEnv, Account::master);
2021
2022 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 3, 2))
2023 .close();
2024
2025 BEAST_EXPECT(door.diff() == STAmount(0));
2026 // att_create_acct_vec return vectors of size 3, so 3 txns
2027 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
2028
2029 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present
2030 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
2031 BEAST_EXPECT(
2032 scEnv.claimCount(jvb) == 1); // claim count still 1
2033 }
2034
2035 {
2036 // resend attestations for 2nd account create => account
2037 // should be created
2038 Balance attester(scEnv, scAttester);
2039 Balance door(scEnv, Account::master);
2040
2041 scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 1)).close();
2042
2043 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2044 BEAST_EXPECT(attester.diff() == -tx_fee);
2045 BEAST_EXPECT(scEnv.balance(scuBob) == amt);
2046
2047 BEAST_EXPECT(!scEnv.caClaimID(jvb, 2)); // claim id 2 deleted
2048 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
2049 BEAST_EXPECT(scEnv.claimCount(jvb) == 2); // claim count now 2
2050 }
2051 {
2052 // resend attestations for 3rc account create => account
2053 // should be created
2054 Balance attester(scEnv, scAttester);
2055 Balance door(scEnv, Account::master);
2056
2057 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1)).close();
2058
2059 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2060 BEAST_EXPECT(attester.diff() == -tx_fee);
2061 BEAST_EXPECT(scEnv.balance(scuCarol) == amt);
2062
2063 BEAST_EXPECT(!scEnv.caClaimID(jvb, 3)); // claim id 3 deleted
2064 BEAST_EXPECT(scEnv.claimCount(jvb) == 3); // claim count now 3
2065 }
2066 }
2067
2068 // Check that creating an account with less than the minimum reserve
2069 // fails.
2070 {
2071 XEnv mcEnv(*this);
2072 XEnv scEnv(*this, true);
2073
2074 auto const amt = res0 - XRP(1);
2075 auto const amt_plus_reward = amt + reward;
2076
2077 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close();
2078
2079 {
2080 Balance door(mcEnv, mcDoor);
2081 Balance carol(mcEnv, mcCarol);
2082
2083 mcEnv
2085 mcCarol, jvb, scuAlice, amt, reward))
2086 .close();
2087
2088 BEAST_EXPECT(door.diff() == amt_plus_reward);
2089 BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee));
2090 }
2091
2094 .close();
2095
2096 Balance attester(scEnv, scAttester);
2097 Balance door(scEnv, Account::master);
2098
2099 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2)).close();
2100 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
2101 BEAST_EXPECT(
2102 scEnv.claimCount(jvb) == 0); // claim count is one less
2103
2104 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 2, 2)).close();
2105 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
2106 BEAST_EXPECT(
2107 scEnv.claimCount(jvb) == 1); // claim count was incremented
2108
2109 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
2110 BEAST_EXPECT(door.diff() == -reward);
2111 BEAST_EXPECT(!scEnv.account(scuAlice));
2112 }
2113
2114 // Check that sending funds with an account create txn to an
2115 // existing account works.
2116 {
2117 XEnv mcEnv(*this);
2118 XEnv scEnv(*this, true);
2119
2120 auto const amt = XRP(111);
2121 auto const amt_plus_reward = amt + reward;
2122
2123 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close();
2124
2125 {
2126 Balance door(mcEnv, mcDoor);
2127 Balance carol(mcEnv, mcCarol);
2128
2129 mcEnv
2131 mcCarol, jvb, scAlice, amt, reward))
2132 .close();
2133
2134 BEAST_EXPECT(door.diff() == amt_plus_reward);
2135 BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee));
2136 }
2137
2140 .close();
2141
2142 Balance attester(scEnv, scAttester);
2143 Balance door(scEnv, Account::master);
2144 Balance alice(scEnv, scAlice);
2145
2146 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2)).close();
2147 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
2148 BEAST_EXPECT(
2149 scEnv.claimCount(jvb) == 0); // claim count is one less
2150
2151 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2, 2)).close();
2152 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
2153 BEAST_EXPECT(
2154 scEnv.claimCount(jvb) == 1); // claim count was incremented
2155
2156 BEAST_EXPECT(door.diff() == -amt_plus_reward);
2157 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
2158 BEAST_EXPECT(alice.diff() == amt);
2159 }
2160
2161 // Check that sending funds to an existing account with deposit auth
2162 // set fails for account create transactions.
2163 {
2164 XEnv mcEnv(*this);
2165 XEnv scEnv(*this, true);
2166
2167 auto const amt = XRP(1000);
2168 auto const amt_plus_reward = amt + reward;
2169
2170 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20))).close();
2171
2172 {
2173 Balance door(mcEnv, mcDoor);
2174 Balance carol(mcEnv, mcCarol);
2175
2176 mcEnv
2178 mcCarol, jvb, scAlice, amt, reward))
2179 .close();
2180
2181 BEAST_EXPECT(door.diff() == amt_plus_reward);
2182 BEAST_EXPECT(carol.diff() == -(amt_plus_reward + tx_fee));
2183 }
2184
2187 .tx(fset("scAlice", asfDepositAuth)) // set deposit auth
2188 .close();
2189
2190 Balance attester(scEnv, scAttester);
2191 Balance door(scEnv, Account::master);
2192 Balance alice(scEnv, scAlice);
2193
2194 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2)).close();
2195 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
2196 BEAST_EXPECT(
2197 scEnv.claimCount(jvb) == 0); // claim count is one less
2198
2199 scEnv.multiTx(att_create_acct_vec(1, amt, scAlice, 2, 2)).close();
2200 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
2201 BEAST_EXPECT(
2202 scEnv.claimCount(jvb) == 1); // claim count was incremented
2203
2204 BEAST_EXPECT(door.diff() == -reward);
2205 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
2206 BEAST_EXPECT(alice.diff() == STAmount(0));
2207 }
2208
2209 // If an account is unable to pay the reserve, check that it fails.
2210 // [greg todo] I don't know what this should test??
2211
2212 // If an attestation already exists for that server and claim id,
2213 // the new attestation should replace the old attestation
2214 {
2215 XEnv mcEnv(*this);
2216 XEnv scEnv(*this, true);
2217 auto const amt = XRP(1000);
2218 auto const amt_plus_reward = amt + reward;
2219
2220 {
2221 Balance door(mcEnv, mcDoor);
2222 Balance carol(mcEnv, mcCarol);
2223
2224 mcEnv.tx(create_bridge(mcDoor, jvb, reward, XRP(20)))
2225 .close()
2227 mcAlice, jvb, scuAlice, amt, reward))
2228 .close() // make sure Alice gets claim #1
2230 mcBob, jvb, scuBob, amt, reward))
2231 .close() // make sure Bob gets claim #2
2233 mcCarol, jvb, scuCarol, amt, reward))
2234 .close(); // and Carol will get claim #3
2235
2236 BEAST_EXPECT(
2237 door.diff() ==
2238 (multiply(amt_plus_reward, STAmount(3), xrpIssue()) -
2239 tx_fee));
2240 BEAST_EXPECT(carol.diff() == -(amt + reward + tx_fee));
2241 }
2242
2243 std::uint32_t const red_quorum = 2;
2245 .tx(jtx::signers(Account::master, red_quorum, signers))
2246 .close();
2247
2248 {
2249 Balance attester(scEnv, scAttester);
2250 Balance door(scEnv, Account::master);
2251 auto const bad_amt = XRP(10);
2252 std::uint32_t txCount = 0;
2253
2254 // send attestations with incorrect amounts to for all 3
2255 // AccountCreate. They will be replaced later
2256 scEnv.multiTx(att_create_acct_vec(1, bad_amt, scuAlice, 1))
2257 .multiTx(att_create_acct_vec(2, bad_amt, scuBob, 1, 2))
2258 .multiTx(att_create_acct_vec(3, bad_amt, scuCarol, 1, 1))
2259 .close();
2260 txCount += 3;
2261
2262 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 1), "claim id 1 created");
2263 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 2), "claim id 2 created");
2264 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 3), "claim id 3 created");
2265
2266 // note: if we send inconsistent attestations in the same
2267 // batch, the transaction errors.
2268
2269 // from now on we send correct attestations
2270 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 1, 0))
2271 .multiTx(att_create_acct_vec(2, amt, scuBob, 1, 2))
2272 .multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 4))
2273 .close();
2274 txCount += 3;
2275
2276 BEAST_EXPECTS(
2277 !!scEnv.caClaimID(jvb, 1), "claim id 1 still there");
2278 BEAST_EXPECTS(
2279 !!scEnv.caClaimID(jvb, 2), "claim id 2 still there");
2280 BEAST_EXPECTS(
2281 !!scEnv.caClaimID(jvb, 3), "claim id 3 still there");
2282 BEAST_EXPECTS(
2283 scEnv.claimCount(jvb) == 0, "No account created yet");
2284
2285 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 1))
2286 .close();
2287 txCount += 1;
2288
2289 BEAST_EXPECTS(
2290 !!scEnv.caClaimID(jvb, 3), "claim id 3 still there");
2291 BEAST_EXPECTS(
2292 scEnv.claimCount(jvb) == 0, "No account created yet");
2293
2294 scEnv.multiTx(att_create_acct_vec(1, amt, scuAlice, 1, 2))
2295 .close();
2296 txCount += 1;
2297
2298 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 deleted");
2299 BEAST_EXPECTS(scEnv.claimCount(jvb) == 1, "scuAlice created");
2300
2301 scEnv.multiTx(att_create_acct_vec(2, amt, scuBob, 1, 3))
2302 .multiTx(
2303 att_create_acct_vec(1, amt, scuAlice, 1, 3),
2305 .close();
2306 txCount += 2;
2307
2308 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 2), "claim id 2 deleted");
2309 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 not added");
2310 BEAST_EXPECTS(
2311 scEnv.claimCount(jvb) == 2, "scuAlice & scuBob created");
2312
2313 scEnv.multiTx(att_create_acct_vec(3, amt, scuCarol, 1, 0))
2314 .close();
2315 txCount += 1;
2316
2317 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 3), "claim id 3 deleted");
2318 BEAST_EXPECTS(
2319 scEnv.claimCount(jvb) == 3, "All 3 accounts created");
2320
2321 // because of the division of the rewards among attesters,
2322 // sometimes a couple drops are left over unspent in the
2323 // door account (here 2 drops)
2324 BEAST_EXPECT(
2325 multiply(amt_plus_reward, STAmount(3), xrpIssue()) +
2326 door.diff() <
2327 drops(3));
2328 BEAST_EXPECT(attester.diff() == -multiTtxFee(txCount));
2329 BEAST_EXPECT(scEnv.balance(scuAlice) == amt);
2330 BEAST_EXPECT(scEnv.balance(scuBob) == amt);
2331 BEAST_EXPECT(scEnv.balance(scuCarol) == amt);
2332 }
2333 }
2334
2335 // If attestation moves funds, confirm the claim ledger objects are
2336 // removed (for both account create and "regular" transactions)
2337 // [greg] we do this in all attestation tests
2338
2339 // coverage test: add_attestation transaction with incorrect flag
2340 {
2341 XEnv scEnv(*this, true);
2344 .close()
2346 scAttester,
2347 jvb,
2348 mcAlice,
2349 XRP(1000),
2350 payees[0],
2351 true,
2352 1,
2353 {},
2354 signers[0]),
2357 .close();
2358 }
2359
2360 // coverage test: add_attestation with xchain feature
2361 // disabled
2362 {
2363 XEnv scEnv(*this, true);
2366 .disableFeature(featureXChainBridge)
2367 .close()
2369 scAttester,
2370 jvb,
2371 mcAlice,
2372 XRP(1000),
2373 payees[0],
2374 true,
2375 1,
2376 {},
2377 signers[0]),
2379 .close();
2380 }
2381 }
2382
2383 void
2385 {
2386 using namespace jtx;
2387
2388 testcase("Add Non Batch Claim Attestation");
2389
2390 {
2391 XEnv mcEnv(*this);
2392 XEnv scEnv(*this, true);
2393 std::uint32_t const claimID = 1;
2394
2395 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2396
2399 .close()
2401 .close();
2402
2403 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
2404
2405 Account const dst{scBob};
2406 auto const amt = XRP(1000);
2407 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2408
2409 auto const dstStartBalance = scEnv.env_.balance(dst);
2410
2411 for (int i = 0; i < signers.size(); ++i)
2412 {
2413 auto const att = claim_attestation(
2414 scAttester,
2415 jvb,
2416 mcAlice,
2417 amt,
2418 payees[i],
2419 true,
2420 claimID,
2421 dst,
2422 signers[i]);
2423
2424 TER const expectedTER =
2426 if (i + 1 == quorum)
2427 scEnv.tx(att, ter(expectedTER)).close();
2428 else
2429 scEnv.tx(att, ter(expectedTER)).close();
2430
2431 if (i + 1 < quorum)
2432 BEAST_EXPECT(dstStartBalance == scEnv.env_.balance(dst));
2433 else
2434 BEAST_EXPECT(
2435 dstStartBalance + amt == scEnv.env_.balance(dst));
2436 }
2437 BEAST_EXPECT(dstStartBalance + amt == scEnv.env_.balance(dst));
2438 }
2439
2440 {
2457 XEnv mcEnv(*this);
2458 XEnv scEnv(*this, true);
2459 auto const amt = XRP(1000);
2460 std::uint32_t const claimID = 1;
2461
2462 for (auto i = 0; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS - 2; ++i)
2463 scEnv.fund(amt, alt_signers[i].account);
2464
2465 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2466
2469 .close()
2471 .close();
2472
2473 Account const dst{scBob};
2474 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2475 auto const dstStartBalance = scEnv.env_.balance(dst);
2476
2477 {
2478 // G1: master key
2479 auto att = claim_attestation(
2480 scAttester,
2481 jvb,
2482 mcAlice,
2483 amt,
2484 payees[0],
2485 true,
2486 claimID,
2487 dst,
2488 alt_signers[0]);
2489 scEnv.tx(att).close();
2490 }
2491 {
2492 // G2: regular key
2493 // alt_signers[0] is the regular key of alt_signers[1]
2494 // There should be 2 attestations after the transaction
2495 scEnv
2496 .tx(jtx::regkey(
2497 alt_signers[1].account, alt_signers[0].account))
2498 .close();
2499 auto att = claim_attestation(
2500 scAttester,
2501 jvb,
2502 mcAlice,
2503 amt,
2504 payees[1],
2505 true,
2506 claimID,
2507 dst,
2508 alt_signers[0]);
2509 att[sfAttestationSignerAccount.getJsonName()] =
2510 alt_signers[1].account.human();
2511 scEnv.tx(att).close();
2512 }
2513 {
2514 // B3: public key and non-exist (unfunded) account mismatch
2515 // G3: public key and non-exist (unfunded) account match
2516 auto const unfundedSigner1 =
2518 auto const unfundedSigner2 =
2520 auto att = claim_attestation(
2521 scAttester,
2522 jvb,
2523 mcAlice,
2524 amt,
2526 true,
2527 claimID,
2528 dst,
2529 unfundedSigner1);
2530 att[sfAttestationSignerAccount.getJsonName()] =
2531 unfundedSigner2.account.human();
2533 .close();
2534 att[sfAttestationSignerAccount.getJsonName()] =
2535 unfundedSigner1.account.human();
2536 scEnv.tx(att).close();
2537 }
2538 {
2539 // B2: single item signer list
2540 std::vector<signer> tempSignerList = {signers[0]};
2541 scEnv.tx(
2542 jtx::signers(alt_signers[2].account, 1, tempSignerList));
2543 auto att = claim_attestation(
2544 scAttester,
2545 jvb,
2546 mcAlice,
2547 amt,
2548 payees[2],
2549 true,
2550 claimID,
2551 dst,
2552 tempSignerList.front());
2553 att[sfAttestationSignerAccount.getJsonName()] =
2554 alt_signers[2].account.human();
2556 .close();
2557 }
2558 {
2559 // B1: disabled master key
2560 scEnv.tx(fset(alt_signers[2].account, asfDisableMaster, 0));
2561 auto att = claim_attestation(
2562 scAttester,
2563 jvb,
2564 mcAlice,
2565 amt,
2566 payees[2],
2567 true,
2568 claimID,
2569 dst,
2570 alt_signers[2]);
2572 .close();
2573 }
2574 {
2575 // --B4: not on signer list
2576 auto att = claim_attestation(
2577 scAttester,
2578 jvb,
2579 mcAlice,
2580 amt,
2581 payees[0],
2582 true,
2583 claimID,
2584 dst,
2585 signers[0]);
2586 scEnv.tx(att, ter(tecNO_PERMISSION)).close();
2587 }
2588 {
2589 // --B5: missing sfAttestationSignerAccount field
2590 // Then submit the one with the field. Should rearch quorum.
2591 auto att = claim_attestation(
2592 scAttester,
2593 jvb,
2594 mcAlice,
2595 amt,
2596 payees[3],
2597 true,
2598 claimID,
2599 dst,
2600 alt_signers[3]);
2601 att.removeMember(sfAttestationSignerAccount.getJsonName());
2602 scEnv.tx(att, ter(temMALFORMED)).close();
2603 BEAST_EXPECT(dstStartBalance == scEnv.env_.balance(dst));
2604 att[sfAttestationSignerAccount.getJsonName()] =
2605 alt_signers[3].account.human();
2606 scEnv.tx(att).close();
2607 BEAST_EXPECT(dstStartBalance + amt == scEnv.env_.balance(dst));
2608 }
2609 }
2610 }
2611
2612 void
2614 {
2615 using namespace jtx;
2616
2617 testcase("Add Non Batch Account Create Attestation");
2618
2619 XEnv mcEnv(*this);
2620 XEnv scEnv(*this, true);
2621
2622 XRPAmount tx_fee = mcEnv.txFee();
2623
2624 Account a{"a"};
2625 Account doorA{"doorA"};
2626
2627 STAmount funds{XRP(10000)};
2628 mcEnv.fund(funds, a);
2629 mcEnv.fund(funds, doorA);
2630
2631 Account ua{"ua"}; // unfunded account we want to create
2632
2633 BridgeDef xrp_b{
2634 doorA,
2635 xrpIssue(),
2637 xrpIssue(),
2638 XRP(1), // reward
2639 XRP(20), // minAccountCreate
2640 4, // quorum
2641 signers,
2643
2644 xrp_b.initBridge(mcEnv, scEnv);
2645
2646 auto const amt = XRP(777);
2647 auto const amt_plus_reward = amt + xrp_b.reward;
2648 {
2649 Balance bal_doorA(mcEnv, doorA);
2650 Balance bal_a(mcEnv, a);
2651
2652 mcEnv
2654 a, xrp_b.jvb, ua, amt, xrp_b.reward))
2655 .close();
2656
2657 BEAST_EXPECT(bal_doorA.diff() == amt_plus_reward);
2658 BEAST_EXPECT(bal_a.diff() == -(amt_plus_reward + tx_fee));
2659 }
2660
2661 for (int i = 0; i < signers.size(); ++i)
2662 {
2663 auto const att = create_account_attestation(
2664 signers[0].account,
2665 xrp_b.jvb,
2666 a,
2667 amt,
2668 xrp_b.reward,
2669 signers[i].account,
2670 true,
2671 1,
2672 ua,
2673 signers[i]);
2674 TER const expectedTER = i < xrp_b.quorum
2675 ? tesSUCCESS
2677
2678 scEnv.tx(att, ter(expectedTER)).close();
2679 if (i + 1 < xrp_b.quorum)
2680 BEAST_EXPECT(!scEnv.env_.le(ua));
2681 else
2682 BEAST_EXPECT(scEnv.env_.le(ua));
2683 }
2684 BEAST_EXPECT(scEnv.env_.le(ua));
2685 }
2686
2687 void
2689 {
2690 using namespace jtx;
2691
2692 XRPAmount res0 = reserve(0);
2693 XRPAmount tx_fee = txFee();
2694
2695 testcase("Claim");
2696
2697 // Claim where the amount matches what is attested to, to an account
2698 // that exists, and there are enough attestations to reach a quorum
2699 // => should succeed
2700 // -----------------------------------------------------------------
2701 for (auto withClaim : {false, true})
2702 {
2703 XEnv mcEnv(*this);
2704 XEnv scEnv(*this, true);
2705
2706 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2707
2710 .close()
2712 .close();
2713
2714 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2715 auto const amt = XRP(1000);
2716 std::uint32_t const claimID = 1;
2717 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2718
2719 BalanceTransfer transfer(
2720 scEnv,
2722 scBob,
2723 scAlice,
2724 &payees[0],
2726 withClaim);
2727
2728 scEnv
2730 scAttester,
2731 jvb,
2732 mcAlice,
2733 amt,
2734 payees,
2735 true,
2736 claimID,
2737 dst,
2738 signers))
2739 .close();
2740 if (withClaim)
2741 {
2742 BEAST_EXPECT(transfer.has_not_happened());
2743
2744 // need to submit a claim transactions
2745 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
2746 .close();
2747 }
2748
2749 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
2750 }
2751
2752 // Claim with just one attestation signed by the Master key
2753 // => should not succeed
2754 // -----------------------------------------------------------------
2755 for (auto withClaim : {false, true})
2756 {
2757 XEnv mcEnv(*this);
2758 XEnv scEnv(*this, true);
2759
2760 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2761
2762 scEnv
2764 //.tx(jtx::signers(Account::master, quorum, signers))
2765 .close()
2767 .close();
2768
2769 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2770 auto const amt = XRP(1000);
2771 std::uint32_t const claimID = 1;
2772 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2773
2774 BalanceTransfer transfer(
2775 scEnv,
2777 scBob,
2778 scAlice,
2779 &payees[0],
2780 1,
2781 withClaim);
2782
2783 jtx::signer master_signer(Account::master);
2784 scEnv
2786 scAttester,
2787 jvb,
2788 mcAlice,
2789 amt,
2790 payees[0],
2791 true,
2792 claimID,
2793 dst,
2794 master_signer),
2796 .close();
2797
2798 BEAST_EXPECT(transfer.has_not_happened());
2799 }
2800
2801 // Claim with just one attestation signed by a regular key
2802 // associated to the master account
2803 // => should not succeed
2804 // -----------------------------------------------------------------
2805 for (auto withClaim : {false, true})
2806 {
2807 XEnv mcEnv(*this);
2808 XEnv scEnv(*this, true);
2809
2810 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2811
2812 scEnv
2814 //.tx(jtx::signers(Account::master, quorum, signers))
2816 .close()
2818 .close();
2819
2820 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2821 auto const amt = XRP(1000);
2822 std::uint32_t const claimID = 1;
2823 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2824
2825 BalanceTransfer transfer(
2826 scEnv,
2828 scBob,
2829 scAlice,
2830 &payees[0],
2831 1,
2832 withClaim);
2833
2834 jtx::signer master_signer(payees[0]);
2835 scEnv
2837 scAttester,
2838 jvb,
2839 mcAlice,
2840 amt,
2841 payees[0],
2842 true,
2843 claimID,
2844 dst,
2845 master_signer),
2847 .close();
2848
2849 BEAST_EXPECT(transfer.has_not_happened());
2850 }
2851
2852 // Claim against non-existent bridge
2853 // ---------------------------------
2854 for (auto withClaim : {false, true})
2855 {
2856 XEnv mcEnv(*this);
2857 XEnv scEnv(*this, true);
2858
2859 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2860
2861 auto jvb_unknown =
2863
2866 .close()
2868 scAlice, jvb_unknown, reward, mcAlice),
2870 .close();
2871
2872 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2873 auto const amt = XRP(1000);
2874 std::uint32_t const claimID = 1;
2875 mcEnv
2876 .tx(xchain_commit(mcAlice, jvb_unknown, claimID, amt, dst),
2878 .close();
2879
2880 BalanceTransfer transfer(
2881 scEnv, Account::master, scBob, scAlice, payees, withClaim);
2882 scEnv
2884 scAttester,
2885 jvb_unknown,
2886 mcAlice,
2887 amt,
2888 payees[0],
2889 true,
2890 claimID,
2891 dst,
2892 signers[0]),
2894 .close();
2895
2896 if (withClaim)
2897 {
2898 BEAST_EXPECT(transfer.has_not_happened());
2899
2900 // need to submit a claim transactions
2901 scEnv
2902 .tx(xchain_claim(scAlice, jvb_unknown, claimID, amt, scBob),
2904 .close();
2905 }
2906
2907 BEAST_EXPECT(transfer.has_not_happened());
2908 }
2909
2910 // Claim against non-existent claim id
2911 // -----------------------------------
2912 for (auto withClaim : {false, true})
2913 {
2914 XEnv mcEnv(*this);
2915 XEnv scEnv(*this, true);
2916
2917 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2918
2921 .close()
2923 .close();
2924
2925 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2926 auto const amt = XRP(1000);
2927 std::uint32_t const claimID = 1;
2928 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2929
2930 BalanceTransfer transfer(
2931 scEnv, Account::master, scBob, scAlice, payees, withClaim);
2932
2933 // attest using non-existent claim id
2934 scEnv
2936 scAttester,
2937 jvb,
2938 mcAlice,
2939 amt,
2940 payees[0],
2941 true,
2942 999,
2943 dst,
2944 signers[0]),
2946 .close();
2947 if (withClaim)
2948 {
2949 BEAST_EXPECT(transfer.has_not_happened());
2950
2951 // claim using non-existent claim id
2952 scEnv
2953 .tx(xchain_claim(scAlice, jvb, 999, amt, scBob),
2955 .close();
2956 }
2957
2958 BEAST_EXPECT(transfer.has_not_happened());
2959 }
2960
2961 // Claim against a claim id owned by another account
2962 // -------------------------------------------------
2963 for (auto withClaim : {false, true})
2964 {
2965 XEnv mcEnv(*this);
2966 XEnv scEnv(*this, true);
2967
2968 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
2969
2972 .close()
2974 .close();
2975
2976 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2977 auto const amt = XRP(1000);
2978 std::uint32_t const claimID = 1;
2979 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
2980
2981 BalanceTransfer transfer(
2982 scEnv,
2984 scBob,
2985 scAlice,
2986 &payees[0],
2988 withClaim);
2989
2990 scEnv
2992 scAttester,
2993 jvb,
2994 mcAlice,
2995 amt,
2996 payees,
2997 true,
2998 claimID,
2999 dst,
3000 signers))
3001 .close();
3002 if (withClaim)
3003 {
3004 BEAST_EXPECT(transfer.has_not_happened());
3005
3006 // submit a claim transaction with the wrong account (scGw
3007 // instead of scAlice)
3008 scEnv
3009 .tx(xchain_claim(scGw, jvb, claimID, amt, scBob),
3011 .close();
3012 BEAST_EXPECT(transfer.has_not_happened());
3013 }
3014 else
3015 {
3016 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3017 }
3018 }
3019
3020 // Claim against a claim id with no attestations
3021 // ---------------------------------------------
3022 for (auto withClaim : {false, true})
3023 {
3024 XEnv mcEnv(*this);
3025 XEnv scEnv(*this, true);
3026
3027 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3028
3031 .close()
3033 .close();
3034
3035 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3036 auto const amt = XRP(1000);
3037 std::uint32_t const claimID = 1;
3038 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3039
3040 BalanceTransfer transfer(
3041 scEnv, Account::master, scBob, scAlice, payees, withClaim);
3042
3043 // don't send any attestations
3044
3045 if (withClaim)
3046 {
3047 BEAST_EXPECT(transfer.has_not_happened());
3048
3049 // need to submit a claim transactions
3050 scEnv
3051 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3053 .close();
3054 }
3055
3056 BEAST_EXPECT(transfer.has_not_happened());
3057 }
3058
3059 // Claim against a claim id with attestations, but not enough to
3060 // make a quorum
3061 // --------------------------------------------------------------------
3062 for (auto withClaim : {false, true})
3063 {
3064 XEnv mcEnv(*this);
3065 XEnv scEnv(*this, true);
3066
3067 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3068
3071 .close()
3073 .close();
3074
3075 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3076 auto const amt = XRP(1000);
3077 std::uint32_t const claimID = 1;
3078 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3079
3080 BalanceTransfer transfer(
3081 scEnv, Account::master, scBob, scAlice, payees, withClaim);
3082
3083 auto tooFew = quorum - 1;
3084 scEnv
3086 scAttester,
3087 jvb,
3088 mcAlice,
3089 amt,
3090 payees,
3091 true,
3092 claimID,
3093 dst,
3094 signers,
3095 tooFew))
3096 .close();
3097 if (withClaim)
3098 {
3099 BEAST_EXPECT(transfer.has_not_happened());
3100
3101 // need to submit a claim transactions
3102 scEnv
3103 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3105 .close();
3106 }
3107
3108 BEAST_EXPECT(transfer.has_not_happened());
3109 }
3110
3111 // Claim id of zero
3112 // ----------------
3113 for (auto withClaim : {false, true})
3114 {
3115 XEnv mcEnv(*this);
3116 XEnv scEnv(*this, true);
3117
3118 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3119
3122 .close()
3124 .close();
3125
3126 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3127 auto const amt = XRP(1000);
3128 std::uint32_t const claimID = 1;
3129 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3130
3131 BalanceTransfer transfer(
3132 scEnv, Account::master, scBob, scAlice, payees, withClaim);
3133
3134 scEnv
3135 .multiTx(
3137 scAttester,
3138 jvb,
3139 mcAlice,
3140 amt,
3141 payees,
3142 true,
3143 0,
3144 dst,
3145 signers),
3147 .close();
3148 if (withClaim)
3149 {
3150 BEAST_EXPECT(transfer.has_not_happened());
3151
3152 // need to submit a claim transactions
3153 scEnv
3154 .tx(xchain_claim(scAlice, jvb, 0, amt, scBob),
3156 .close();
3157 }
3158
3159 BEAST_EXPECT(transfer.has_not_happened());
3160 }
3161
3162 // Claim issue that does not match the expected issue on the bridge
3163 // (either LockingChainIssue or IssuingChainIssue, depending on the
3164 // chain). The claim id should already have enough attestations to
3165 // reach a quorum for this amount (for a different issuer).
3166 // ---------------------------------------------------------------------
3167 for (auto withClaim : {true})
3168 {
3169 XEnv mcEnv(*this);
3170 XEnv scEnv(*this, true);
3171
3172 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3173
3176 .close()
3178 .close();
3179
3180 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3181 auto const amt = XRP(1000);
3182 std::uint32_t const claimID = 1;
3183 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3184
3185 BalanceTransfer transfer(
3186 scEnv,
3188 scBob,
3189 scAlice,
3190 &payees[0],
3192 withClaim);
3193
3194 scEnv
3196 scAttester,
3197 jvb,
3198 mcAlice,
3199 amt,
3200 payees,
3201 true,
3202 claimID,
3203 dst,
3204 signers))
3205 .close();
3206
3207 if (withClaim)
3208 {
3209 BEAST_EXPECT(transfer.has_not_happened());
3210
3211 // need to submit a claim transactions
3212 scEnv
3213 .tx(xchain_claim(scAlice, jvb, claimID, scUSD(1000), scBob),
3215 .close();
3216 }
3217
3218 BEAST_EXPECT(transfer.has_not_happened());
3219 }
3220
3221 // Claim to a destination that does not already exist on the chain
3222 // -----------------------------------------------------------------
3223 for (auto withClaim : {true})
3224 {
3225 XEnv mcEnv(*this);
3226 XEnv scEnv(*this, true);
3227
3228 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3229
3232 .close()
3234 .close();
3235
3236 auto dst(withClaim ? std::nullopt : std::optional<Account>{scuBob});
3237 auto const amt = XRP(1000);
3238 std::uint32_t const claimID = 1;
3239 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3240
3241 BalanceTransfer transfer(
3242 scEnv,
3244 scBob,
3245 scAlice,
3246 &payees[0],
3248 withClaim);
3249
3250 scEnv
3252 scAttester,
3253 jvb,
3254 mcAlice,
3255 amt,
3256 payees,
3257 true,
3258 claimID,
3259 dst,
3260 signers))
3261 .close();
3262 if (withClaim)
3263 {
3264 BEAST_EXPECT(transfer.has_not_happened());
3265
3266 // need to submit a claim transactions
3267 scEnv
3268 .tx(xchain_claim(scAlice, jvb, claimID, amt, scuBob),
3269 ter(tecNO_DST))
3270 .close();
3271 }
3272
3273 BEAST_EXPECT(transfer.has_not_happened());
3274 }
3275
3276 // Claim where the claim id owner does not have enough XRP to pay
3277 // the reward
3278 // ------------------------------------------------------------------
3279 for (auto withClaim : {false, true})
3280 {
3281 XEnv mcEnv(*this);
3282 XEnv scEnv(*this, true);
3283
3284 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3285 STAmount huge_reward{XRP(20000)};
3286 BEAST_EXPECT(huge_reward > scEnv.balance(scAlice));
3287
3288 scEnv.tx(create_bridge(Account::master, jvb, huge_reward))
3290 .close()
3291 .tx(xchain_create_claim_id(scAlice, jvb, huge_reward, mcAlice))
3292 .close();
3293
3294 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3295 auto const amt = XRP(1000);
3296 std::uint32_t const claimID = 1;
3297 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3298
3299 BalanceTransfer transfer(
3300 scEnv,
3302 scBob,
3303 scAlice,
3304 &payees[0],
3306 withClaim);
3307
3308 if (withClaim)
3309 {
3310 scEnv
3312 scAttester,
3313 jvb,
3314 mcAlice,
3315 amt,
3316 payees,
3317 true,
3318 claimID,
3319 dst,
3320 signers))
3321 .close();
3322 BEAST_EXPECT(transfer.has_not_happened());
3323
3324 // need to submit a claim transactions
3325 scEnv
3326 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3328 .close();
3329 }
3330 else
3331 {
3332 auto txns = claim_attestations(
3333 scAttester,
3334 jvb,
3335 mcAlice,
3336 amt,
3337 payees,
3338 true,
3339 claimID,
3340 dst,
3341 signers);
3342 for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i)
3343 {
3344 scEnv.tx(txns[i]).close();
3345 }
3346 scEnv.tx(txns.back());
3347 scEnv.close();
3348 // The attestation should succeed, because it adds an
3349 // attestation, but the claim should fail with insufficient
3350 // funds
3351 scEnv
3352 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3354 .close();
3355 }
3356
3357 BEAST_EXPECT(transfer.has_not_happened());
3358 }
3359
3360 // Claim where the claim id owner has enough XRP to pay the reward,
3361 // but it would put his balance below the reserve
3362 // --------------------------------------------------------------------
3363 for (auto withClaim : {false, true})
3364 {
3365 XEnv mcEnv(*this);
3366 XEnv scEnv(*this, true);
3367
3368 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3369
3372 .fund(
3373 res0 + reward,
3374 scuAlice) // just not enough because of fees
3375 .close()
3378 .close();
3379
3380 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3381 auto const amt = XRP(1000);
3382 std::uint32_t const claimID = 1;
3383 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3384
3385 BalanceTransfer transfer(
3386 scEnv, Account::master, scBob, scuAlice, payees, withClaim);
3387
3388 scEnv
3390 scAttester,
3391 jvb,
3392 mcAlice,
3393 amt,
3394 payees[0],
3395 true,
3396 claimID,
3397 dst,
3398 signers[0]),
3400 .close();
3401 if (withClaim)
3402 {
3403 BEAST_EXPECT(transfer.has_not_happened());
3404
3405 // need to submit a claim transactions
3406 scEnv
3407 .tx(xchain_claim(scuAlice, jvb, claimID, amt, scBob),
3409 .close();
3410 }
3411
3412 BEAST_EXPECT(transfer.has_not_happened());
3413 }
3414
3415 // Pay to an account with deposit auth set
3416 // ---------------------------------------
3417 for (auto withClaim : {false, true})
3418 {
3419 XEnv mcEnv(*this);
3420 XEnv scEnv(*this, true);
3421
3422 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3423
3426 .tx(fset("scBob", asfDepositAuth)) // set deposit auth
3427 .close()
3429 .close();
3430
3431 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3432 auto const amt = XRP(1000);
3433 std::uint32_t const claimID = 1;
3434 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3435
3436 BalanceTransfer transfer(
3437 scEnv,
3439 scBob,
3440 scAlice,
3441 &payees[0],
3443 withClaim);
3444 auto txns = claim_attestations(
3445 scAttester,
3446 jvb,
3447 mcAlice,
3448 amt,
3449 payees,
3450 true,
3451 claimID,
3452 dst,
3453 signers);
3454 for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i)
3455 {
3456 scEnv.tx(txns[i]).close();
3457 }
3458 if (withClaim)
3459 {
3460 scEnv.tx(txns.back()).close();
3461
3462 BEAST_EXPECT(transfer.has_not_happened());
3463
3464 // need to submit a claim transactions
3465 scEnv
3466 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3468 .close();
3469
3470 // the transfer failed, but check that we can still use the
3471 // claimID with a different account
3472 Balance scCarol_bal(scEnv, scCarol);
3473
3474 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol))
3475 .close();
3476 BEAST_EXPECT(scCarol_bal.diff() == amt);
3477 }
3478 else
3479 {
3480 scEnv.tx(txns.back()).close();
3481 scEnv
3482 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3484 .close();
3485 // A way would be to remove deposit auth and resubmit the
3486 // attestations (even though the witness servers won't do
3487 // it)
3488 scEnv
3489 .tx(fset("scBob", 0, asfDepositAuth)) // clear deposit auth
3490 .close();
3491
3492 Balance scBob_bal(scEnv, scBob);
3493 scEnv.tx(txns.back()).close();
3494 BEAST_EXPECT(scBob_bal.diff() == amt);
3495 }
3496 }
3497
3498 // Pay to an account with Destination Tag set
3499 // ------------------------------------------
3500 for (auto withClaim : {false, true})
3501 {
3502 XEnv mcEnv(*this);
3503 XEnv scEnv(*this, true);
3504
3505 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3506
3509 .tx(fset("scBob", asfRequireDest)) // set dest tag
3510 .close()
3512 .close();
3513
3514 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3515 auto const amt = XRP(1000);
3516 std::uint32_t const claimID = 1;
3517 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3518
3519 BalanceTransfer transfer(
3520 scEnv,
3522 scBob,
3523 scAlice,
3524 &payees[0],
3526 withClaim);
3527 auto txns = claim_attestations(
3528 scAttester,
3529 jvb,
3530 mcAlice,
3531 amt,
3532 payees,
3533 true,
3534 claimID,
3535 dst,
3536 signers);
3537 for (int i = 0; i < UT_XCHAIN_DEFAULT_QUORUM - 1; ++i)
3538 {
3539 scEnv.tx(txns[i]).close();
3540 }
3541 if (withClaim)
3542 {
3543 scEnv.tx(txns.back()).close();
3544 BEAST_EXPECT(transfer.has_not_happened());
3545
3546 // need to submit a claim transactions
3547 scEnv
3548 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3550 .close();
3551
3552 // the transfer failed, but check that we can still use the
3553 // claimID with a different account
3554 Balance scCarol_bal(scEnv, scCarol);
3555
3556 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol))
3557 .close();
3558 BEAST_EXPECT(scCarol_bal.diff() == amt);
3559 }
3560 else
3561 {
3562 scEnv.tx(txns.back()).close();
3563 scEnv
3564 .tx(xchain_claim(scAlice, jvb, claimID, amt, scBob),
3566 .close();
3567 // A way would be to remove the destination tag requirement
3568 // and resubmit the attestations (even though the witness
3569 // servers won't do it)
3570 scEnv
3571 .tx(fset("scBob", 0, asfRequireDest)) // clear dest tag
3572 .close();
3573
3574 Balance scBob_bal(scEnv, scBob);
3575
3576 scEnv.tx(txns.back()).close();
3577 BEAST_EXPECT(scBob_bal.diff() == amt);
3578 }
3579 }
3580
3581 // Pay to an account with deposit auth set. Check that the attestations
3582 // are still validated and that we can used the claimID to transfer the
3583 // funds to a different account (which doesn't have deposit auth set)
3584 // --------------------------------------------------------------------
3585 {
3586 XEnv mcEnv(*this);
3587 XEnv scEnv(*this, true);
3588
3589 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3590
3593 .tx(fset("scBob", asfDepositAuth)) // set deposit auth
3594 .close()
3596 .close();
3597
3598 auto dst(std::optional<Account>{scBob});
3599 auto const amt = XRP(1000);
3600 std::uint32_t const claimID = 1;
3601 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3602
3603 // we should be able to submit the attestations, but the transfer
3604 // should not occur because dest account has deposit auth set
3605 Balance scBob_bal(scEnv, scBob);
3606
3608 scAttester,
3609 jvb,
3610 mcAlice,
3611 amt,
3612 payees,
3613 true,
3614 claimID,
3615 dst,
3616 signers));
3617 BEAST_EXPECT(scBob_bal.diff() == STAmount(0));
3618
3619 // Check that check that we still can use the claimID to transfer
3620 // the amount to a different account
3621 Balance scCarol_bal(scEnv, scCarol);
3622
3623 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scCarol)).close();
3624 BEAST_EXPECT(scCarol_bal.diff() == amt);
3625 }
3626
3627 // Claim where the amount different from what is attested to
3628 // ---------------------------------------------------------
3629 for (auto withClaim : {true})
3630 {
3631 XEnv mcEnv(*this);
3632 XEnv scEnv(*this, true);
3633
3634 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3635
3638 .close()
3640 .close();
3641
3642 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3643 auto const amt = XRP(1000);
3644 std::uint32_t const claimID = 1;
3645 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3646
3647 BalanceTransfer transfer(
3648 scEnv,
3650 scBob,
3651 scAlice,
3652 &payees[0],
3654 withClaim);
3656 scAttester,
3657 jvb,
3658 mcAlice,
3659 amt,
3660 payees,
3661 true,
3662 claimID,
3663 dst,
3664 signers));
3665 if (withClaim)
3666 {
3667 BEAST_EXPECT(transfer.has_not_happened());
3668
3669 // claim wrong amount
3670 scEnv
3671 .tx(xchain_claim(scAlice, jvb, claimID, one_xrp, scBob),
3673 .close();
3674 }
3675
3676 BEAST_EXPECT(transfer.has_not_happened());
3677 }
3678
3679 // Verify that rewards are paid from the account that owns the claim
3680 // id
3681 // --------------------------------------------------------------------
3682 for (auto withClaim : {false, true})
3683 {
3684 XEnv mcEnv(*this);
3685 XEnv scEnv(*this, true);
3686
3687 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3688
3691 .close()
3693 .close();
3694
3695 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3696 auto const amt = XRP(1000);
3697 std::uint32_t const claimID = 1;
3698 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3699
3700 BalanceTransfer transfer(
3701 scEnv,
3703 scBob,
3704 scAlice,
3705 &payees[0],
3707 withClaim);
3708 Balance scAlice_bal(scEnv, scAlice);
3710 scAttester,
3711 jvb,
3712 mcAlice,
3713 amt,
3714 payees,
3715 true,
3716 claimID,
3717 dst,
3718 signers));
3719
3720 STAmount claim_cost = reward;
3721
3722 if (withClaim)
3723 {
3724 BEAST_EXPECT(transfer.has_not_happened());
3725
3726 // need to submit a claim transactions
3727 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3728 .close();
3729 claim_cost += tx_fee;
3730 }
3731
3732 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3733 BEAST_EXPECT(
3734 scAlice_bal.diff() == -claim_cost); // because reward % 4 == 0
3735 }
3736
3737 // Verify that if a reward is not evenly divisible among the reward
3738 // accounts, the remaining amount goes to the claim id owner.
3739 // ----------------------------------------------------------------
3740 for (auto withClaim : {false, true})
3741 {
3742 XEnv mcEnv(*this);
3743 XEnv scEnv(*this, true);
3744
3746
3749 .close()
3751 .close();
3752
3753 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3754 auto const amt = XRP(1000);
3755 std::uint32_t const claimID = 1;
3756 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3757
3758 BalanceTransfer transfer(
3759 scEnv,
3761 scBob,
3762 scAlice,
3763 &payees[0],
3765 withClaim);
3766 Balance scAlice_bal(scEnv, scAlice);
3768 scAttester,
3769 jvb,
3770 mcAlice,
3771 amt,
3772 payees,
3773 true,
3774 claimID,
3775 dst,
3776 signers));
3777 STAmount claim_cost = tiny_reward;
3778
3779 if (withClaim)
3780 {
3781 BEAST_EXPECT(transfer.has_not_happened());
3782
3783 // need to submit a claim transactions
3784 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3785 .close();
3786 claim_cost += tx_fee;
3787 }
3788
3789 BEAST_EXPECT(transfer.has_happened(amt, tiny_reward_split));
3790 BEAST_EXPECT(
3791 scAlice_bal.diff() == -(claim_cost - tiny_reward_remainder));
3792 }
3793
3794 // If a reward distribution fails for one of the reward accounts
3795 // (the reward account doesn't exist or has deposit auth set), then
3796 // the txn should still succeed, but that portion should go to the
3797 // claim id owner.
3798 // -------------------------------------------------------------------
3799 for (auto withClaim : {false, true})
3800 {
3801 XEnv mcEnv(*this);
3802 XEnv scEnv(*this, true);
3803
3804 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3805
3806 std::vector<Account> alt_payees{payees.begin(), payees.end() - 1};
3807 alt_payees.back() = Account("inexistent");
3808
3811 .close()
3813 .close();
3814
3815 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3816 auto const amt = XRP(1000);
3817 std::uint32_t const claimID = 1;
3818 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3819
3820 BalanceTransfer transfer(
3821 scEnv,
3823 scBob,
3824 scAlice,
3825 &payees[0],
3827 withClaim);
3829 scAttester,
3830 jvb,
3831 mcAlice,
3832 amt,
3833 alt_payees,
3834 true,
3835 claimID,
3836 dst,
3837 signers));
3838
3839 if (withClaim)
3840 {
3841 BEAST_EXPECT(transfer.has_not_happened());
3842
3843 // need to submit a claim transactions
3844 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3845 .close();
3846 }
3847
3848 // this also checks that only 3 * split_reward was deducted from
3849 // scAlice (the payor account), since we passed alt_payees to
3850 // BalanceTransfer
3851 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3852 }
3853
3854 for (auto withClaim : {false, true})
3855 {
3856 XEnv mcEnv(*this);
3857 XEnv scEnv(*this, true);
3858
3859 mcEnv.tx(create_bridge(mcDoor, jvb)).close();
3860 auto& unpaid = payees[UT_XCHAIN_DEFAULT_QUORUM - 1];
3863 .tx(fset(unpaid, asfDepositAuth))
3864 .close()
3866 .close();
3867
3868 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3869 auto const amt = XRP(1000);
3870 std::uint32_t const claimID = 1;
3871 mcEnv.tx(xchain_commit(mcAlice, jvb, claimID, amt, dst)).close();
3872
3873 // balance of last signer should not change (has deposit auth)
3874 Balance last_signer(scEnv, unpaid);
3875
3876 // make sure all signers except the last one get the
3877 // split_reward
3878
3879 BalanceTransfer transfer(
3880 scEnv,
3882 scBob,
3883 scAlice,
3884 &payees[0],
3886 withClaim);
3888 scAttester,
3889 jvb,
3890 mcAlice,
3891 amt,
3892 payees,
3893 true,
3894 claimID,
3895 dst,
3896 signers));
3897
3898 if (withClaim)
3899 {
3900 BEAST_EXPECT(transfer.has_not_happened());
3901
3902 // need to submit a claim transactions
3903 scEnv.tx(xchain_claim(scAlice, jvb, claimID, amt, scBob))
3904 .close();
3905 }
3906
3907 // this also checks that only 3 * split_reward was deducted from
3908 // scAlice (the payor account), since we passed payees.size() -
3909 // 1 to BalanceTransfer
3910 BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum));
3911
3912 // and make sure the account with deposit auth received nothing
3913 BEAST_EXPECT(last_signer.diff() == STAmount(0));
3914 }
3915
3916 // coverage test: xchain_claim transaction with incorrect flag
3917 XEnv(*this, true)
3919 .close()
3920 .tx(xchain_claim(scAlice, jvb, 1, XRP(1000), scBob),
3923 .close();
3924
3925 // coverage test: xchain_claim transaction with xchain feature
3926 // disabled
3927 XEnv(*this, true)
3929 .disableFeature(featureXChainBridge)
3930 .close()
3931 .tx(xchain_claim(scAlice, jvb, 1, XRP(1000), scBob),
3933 .close();
3934
3935 // coverage test: XChainClaim::preclaim - isLockingChain = true;
3936 XEnv(*this)
3938 .close()
3939 .tx(xchain_claim(mcAlice, jvb, 1, XRP(1000), mcBob),
3941 }
3942
3943 void
3945 {
3946 using namespace jtx;
3947
3948 testcase("Bridge Create Account");
3949 XRPAmount tx_fee = txFee();
3950
3951 // coverage test: transferHelper() - dst == src
3952 {
3953 XEnv scEnv(*this, true);
3954
3955 auto const amt = XRP(111);
3956 auto const amt_plus_reward = amt + reward;
3957
3960 .close();
3961
3962 Balance door(scEnv, Account::master);
3963
3964 // scEnv.tx(att_create_acct_batch1(1, amt,
3965 // Account::master)).close();
3967 .close();
3968 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
3969 BEAST_EXPECT(
3970 scEnv.claimCount(jvb) == 0); // claim count is one less
3971
3972 // scEnv.tx(att_create_acct_batch2(1, amt,
3973 // Account::master)).close();
3974 scEnv.multiTx(att_create_acct_vec(1, amt, Account::master, 2, 2))
3975 .close();
3976 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
3977 BEAST_EXPECT(
3978 scEnv.claimCount(jvb) == 1); // claim count was incremented
3979
3980 BEAST_EXPECT(door.diff() == -reward);
3981 }
3982
3983 // Check that creating an account with less than the minimum create
3984 // amount fails.
3985 {
3986 XEnv mcEnv(*this);
3987
3988 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3989
3990 Balance door(mcEnv, mcDoor);
3991 Balance carol(mcEnv, mcCarol);
3992
3993 mcEnv
3995 mcCarol, jvb, scuAlice, XRP(19), reward),
3997 .close();
3998
3999 BEAST_EXPECT(door.diff() == STAmount(0));
4000 BEAST_EXPECT(carol.diff() == -tx_fee);
4001 }
4002
4003 // Check that creating an account with invalid flags fails.
4004 {
4005 XEnv mcEnv(*this);
4006
4007 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4008
4009 Balance door(mcEnv, mcDoor);
4010
4011 mcEnv
4013 mcCarol, jvb, scuAlice, XRP(20), reward),
4016 .close();
4017
4018 BEAST_EXPECT(door.diff() == STAmount(0));
4019 }
4020
4021 // Check that creating an account with the XChainBridge feature
4022 // disabled fails.
4023 {
4024 XEnv mcEnv(*this);
4025
4026 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4027
4028 Balance door(mcEnv, mcDoor);
4029
4030 mcEnv.disableFeature(featureXChainBridge)
4032 mcCarol, jvb, scuAlice, XRP(20), reward),
4034 .close();
4035
4036 BEAST_EXPECT(door.diff() == STAmount(0));
4037 }
4038
4039 // Check that creating an account with a negative amount fails
4040 {
4041 XEnv mcEnv(*this);
4042
4043 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4044
4045 Balance door(mcEnv, mcDoor);
4046
4047 mcEnv
4049 mcCarol, jvb, scuAlice, XRP(-20), reward),
4051 .close();
4052
4053 BEAST_EXPECT(door.diff() == STAmount(0));
4054 }
4055
4056 // Check that creating an account with a negative reward fails
4057 {
4058 XEnv mcEnv(*this);
4059
4060 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4061
4062 Balance door(mcEnv, mcDoor);
4063
4064 mcEnv
4066 mcCarol, jvb, scuAlice, XRP(20), XRP(-1)),
4068 .close();
4069
4070 BEAST_EXPECT(door.diff() == STAmount(0));
4071 }
4072
4073 // Check that door account can't lock funds onto itself
4074 {
4075 XEnv mcEnv(*this);
4076
4077 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4078
4079 Balance door(mcEnv, mcDoor);
4080
4081 mcEnv
4083 mcDoor, jvb, scuAlice, XRP(20), XRP(1)),
4085 .close();
4086
4087 BEAST_EXPECT(door.diff() == -tx_fee);
4088 }
4089
4090 // Check that reward matches the amount specified in bridge
4091 {
4092 XEnv mcEnv(*this);
4093
4094 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(20))).close();
4095
4096 Balance door(mcEnv, mcDoor);
4097
4098 mcEnv
4100 mcCarol, jvb, scuAlice, XRP(20), XRP(2)),
4102 .close();
4103
4104 BEAST_EXPECT(door.diff() == STAmount(0));
4105 }
4106 }
4107
4108 void
4110 {
4111 using namespace jtx;
4112 XRPAmount res0 = reserve(0);
4113 XRPAmount tx_fee = txFee();
4114
4115 testcase("Fee dips into reserve");
4116
4117 // commit where the fee dips into the reserve, this should succeed
4118 XEnv(*this)
4120 .fund(res0 + one_xrp + tx_fee - drops(1), mcuAlice)
4121 .close()
4123 ter(tesSUCCESS));
4124
4125 // commit where the commit amount drips into the reserve, this should
4126 // fail
4127 XEnv(*this)
4129 .fund(res0 + one_xrp - drops(1), mcuAlice)
4130 .close()
4133
4134 auto const minAccountCreate = XRP(20);
4135
4136 // account create commit where the fee dips into the reserve,
4137 // this should succeed
4138 XEnv(*this)
4139 .tx(create_bridge(mcDoor, jvb, reward, minAccountCreate))
4140 .fund(
4141 res0 + tx_fee + minAccountCreate + reward - drops(1), mcuAlice)
4142 .close()
4144 mcuAlice, jvb, scuAlice, minAccountCreate, reward),
4145 ter(tesSUCCESS));
4146
4147 // account create commit where the commit dips into the reserve,
4148 // this should fail
4149 XEnv(*this)
4150 .tx(create_bridge(mcDoor, jvb, reward, minAccountCreate))
4151 .fund(res0 + minAccountCreate + reward - drops(1), mcuAlice)
4152 .close()
4154 mcuAlice, jvb, scuAlice, minAccountCreate, reward),
4156 }
4157
4158 void
4160 {
4161 using namespace jtx;
4162
4163 testcase("Bridge Delete Door Account");
4164
4165 auto const acctDelFee{
4166 drops(XEnv(*this).env_.current()->fees().increment)};
4167
4168 // Deleting an account that owns bridge should fail
4169 {
4170 XEnv mcEnv(*this);
4171
4172 mcEnv.tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1))).close();
4173
4174 // We don't allow an account to be deleted if its sequence
4175 // number is within 256 of the current ledger.
4176 for (size_t i = 0; i < 256; ++i)
4177 mcEnv.close();
4178
4179 // try to delete mcDoor, send funds to mcAlice
4180 mcEnv.tx(
4182 fee(acctDelFee),
4184 }
4185
4186 // Deleting an account that owns a claim id should fail
4187 {
4188 XEnv scEnv(*this, true);
4189
4191 .close()
4193 .close();
4194
4195 // We don't allow an account to be deleted if its sequence
4196 // number is within 256 of the current ledger.
4197 for (size_t i = 0; i < 256; ++i)
4198 scEnv.close();
4199
4200 // try to delete scAlice, send funds to scBob
4201 scEnv.tx(
4203 fee(acctDelFee),
4205 }
4206 }
4207
4208 void
4210 {
4211 using namespace jtx;
4212
4213 testcase("Bad attestations");
4214 {
4215 // Create a bridge and add an attestation with a bad public key
4216 XEnv scEnv(*this, true);
4217 std::uint32_t const claimID = 1;
4219 auto const amt = XRP(1000);
4222 .close();
4224 .close();
4225 auto jvAtt = claim_attestation(
4226 scAttester,
4227 jvb,
4228 mcAlice,
4229 amt,
4231 true,
4232 claimID,
4233 dst,
4235 {
4236 // Change to an invalid keytype
4237 auto k = jvAtt["PublicKey"].asString();
4238 k.at(1) = '9';
4239 jvAtt["PublicKey"] = k;
4240 }
4241 scEnv.tx(jvAtt, ter(temMALFORMED)).close();
4242 }
4243 {
4244 // Create a bridge and add an create account attestation with a bad
4245 // public key
4246 XEnv scEnv(*this, true);
4247 std::uint32_t const createCount = 1;
4248 Account dst{scBob};
4249 auto const amt = XRP(1000);
4250 auto const rewardAmt = XRP(1);
4253 .close();
4254 auto jvAtt = create_account_attestation(
4255 scAttester,
4256 jvb,
4257 mcAlice,
4258 amt,
4259 rewardAmt,
4261 true,
4262 createCount,
4263 dst,
4265 {
4266 // Change to an invalid keytype
4267 auto k = jvAtt["PublicKey"].asString();
4268 k.at(1) = '9';
4269 jvAtt["PublicKey"] = k;
4270 }
4271 scEnv.tx(jvAtt, ter(temMALFORMED)).close();
4272 }
4273 }
4274
4275 void
4276 run() override
4277 {
4293 }
4294};
4295
4296// -----------------------------------------------------------
4297// -----------------------------------------------------------
4300{
4301private:
4302 static constexpr size_t num_signers = 5;
4303
4304 // --------------------------------------------------
4305 enum class WithClaim { no, yes };
4307 {
4312 bool a2b; // direction of transfer
4314 uint32_t claim_id{0};
4316 };
4317
4319 {
4324 bool a2b;
4325 uint32_t claim_id{0};
4327 };
4328
4330 using BridgeID = BridgeDef const*;
4331
4332 // tracking chain state
4333 // --------------------
4335 {
4338
4339 void
4340 init(ENV& env, jtx::Account const& acct)
4341 {
4342 startAmount = env.balance(acct);
4344 }
4345
4346 bool
4347 verify(ENV& env, jtx::Account const& acct) const
4348 {
4349 STAmount diff{env.balance(acct) - startAmount};
4350 bool check = diff == expectedDiff;
4351 return check;
4352 }
4353 };
4354
4355 // --------------------------------------------------
4357 {
4361
4363 : env(env), tx_fee(env.env_.current()->fees().base)
4364 {
4365 }
4366
4367 void
4368 sendAttestations(size_t signer_idx, BridgeID bridge, ClaimVec& claims)
4369 {
4370 for (auto const& c : claims)
4371 {
4372 env.tx(c).close();
4373 spendFee(bridge->signers[signer_idx].account);
4374 }
4375 claims.clear();
4376 }
4377
4378 uint32_t
4380 size_t signer_idx,
4382 CreateClaimVec& claims)
4383 {
4384 size_t num_successful = 0;
4385 for (auto const& c : claims)
4386 {
4387 env.tx(c).close();
4388 if (env.ter() == tesSUCCESS)
4389 {
4390 counters[bridge].signers.push_back(signer_idx);
4391 num_successful++;
4392 }
4393 spendFee(bridge->signers[signer_idx].account);
4394 }
4395 claims.clear();
4396 return num_successful;
4397 }
4398
4399 void
4401 {
4402 bool callback_called;
4403
4404 // we have this "do {} while" loop because we want to process
4405 // all the account create which can reach quorum at this time
4406 // stamp.
4407 do
4408 {
4409 callback_called = false;
4410 for (size_t i = 0; i < signers_attns.size(); ++i)
4411 {
4412 for (auto& [bridge, claims] : signers_attns[i])
4413 {
4414 sendAttestations(i, bridge, claims.xfer_claims);
4415
4416 auto& c = counters[bridge];
4417 auto& create_claims =
4418 claims.create_claims[c.claim_count];
4419 auto num_attns = create_claims.size();
4420 if (num_attns)
4421 {
4422 c.num_create_attn_sent += sendCreateAttestations(
4423 i, bridge, create_claims);
4424 }
4425 assert(claims.create_claims[c.claim_count].empty());
4426 }
4427 }
4428 for (auto& [bridge, c] : counters)
4429 {
4430 if (c.num_create_attn_sent >= bridge->quorum)
4431 {
4432 callback_called = true;
4433 c.create_callbacks[c.claim_count](c.signers);
4434 ++c.claim_count;
4435 c.num_create_attn_sent = 0;
4436 c.signers.clear();
4437 }
4438 }
4439 } while (callback_called);
4440 }
4441
4442 void
4443 init(jtx::Account const& acct)
4444 {
4445 accounts[acct].init(env, acct);
4446 }
4447
4448 void
4450 jtx::Account const& acct,
4451 STAmount amt,
4452 std::uint64_t divisor = 1)
4453 {
4454 if (amt.issue() != xrpIssue())
4455 return;
4456 auto it = accounts.find(acct);
4457 if (it == accounts.end())
4458 {
4459 accounts[acct].init(env, acct);
4460 // we just looked up the account, so expectedDiff == 0
4461 }
4462 else
4463 {
4464 it->second.expectedDiff +=
4465 (divisor == 1 ? amt
4466 : divide(
4467 amt,
4468 STAmount(amt.issue(), divisor),
4469 amt.issue()));
4470 }
4471 }
4472
4473 void
4474 spend(jtx::Account const& acct, STAmount amt, std::uint64_t times = 1)
4475 {
4476 if (amt.issue() != xrpIssue())
4477 return;
4478 receive(
4479 acct,
4480 times == 1
4481 ? -amt
4482 : -multiply(
4483 amt, STAmount(amt.issue(), times), amt.issue()));
4484 }
4485
4486 void
4487 transfer(jtx::Account const& from, jtx::Account const& to, STAmount amt)
4488 {
4489 spend(from, amt);
4490 receive(to, amt);
4491 }
4492
4493 void
4494 spendFee(jtx::Account const& acct, size_t times = 1)
4495 {
4496 spend(acct, tx_fee, times);
4497 }
4498
4499 bool
4500 verify() const
4501 {
4502 for (auto const& [acct, state] : accounts)
4503 if (!state.verify(env, acct))
4504 return false;
4505 return true;
4506 }
4507
4509 {
4512
4513 uint32_t claim_id{0};
4514 uint32_t create_count{0}; // for account create. First should be 1
4515 uint32_t claim_count{
4516 0}; // for account create. Increments after quorum for
4517 // current create_count (starts at 1) is reached.
4518
4519 uint32_t num_create_attn_sent{0}; // for current claim_count
4522 };
4523
4524 struct Claims
4525 {
4528 };
4529
4532
4538 };
4539
4541 {
4542 ChainStateTracker(ENV& a_env, ENV& b_env) : a_(a_env), b_(b_env)
4543 {
4544 }
4545
4546 bool
4547 verify() const
4548 {
4549 return a_.verify() && b_.verify();
4550 }
4551
4552 void
4554 {
4557 }
4558
4559 void
4560 init(jtx::Account const& acct)
4561 {
4562 a_.init(acct);
4563 b_.init(acct);
4564 }
4565
4568 };
4569
4570 enum SmState {
4577 };
4578
4579 enum Act_Flags { af_a2b = 1 << 0 };
4580
4581 // --------------------------------------------------
4582 template <class T>
4584 {
4585 public:
4587 const std::shared_ptr<ChainStateTracker>& chainstate,
4588 const BridgeDef& bridge)
4589 : bridge_(bridge), st_(chainstate)
4590 {
4591 }
4592
4595 {
4596 return static_cast<T&>(*this).a2b() ? st_->a_ : st_->b_;
4597 }
4598
4601 {
4602 return static_cast<T&>(*this).a2b() ? st_->b_ : st_->a_;
4603 }
4604
4605 jtx::Account const&
4607 {
4608 return static_cast<T&>(*this).a2b() ? bridge_.doorA : bridge_.doorB;
4609 }
4610
4611 jtx::Account const&
4613 {
4614 return static_cast<T&>(*this).a2b() ? bridge_.doorB : bridge_.doorA;
4615 }
4616
4617 protected:
4620 };
4621
4622 // --------------------------------------------------
4623 class SmCreateAccount : public SmBase<SmCreateAccount>
4624 {
4625 public:
4627
4629 const std::shared_ptr<ChainStateTracker>& chainstate,
4630 const BridgeDef& bridge,
4632 : Base(chainstate, bridge)
4634 , cr(std::move(create))
4635 {
4636 }
4637
4638 bool
4639 a2b() const
4640 {
4641 return cr.a2b;
4642 }
4643
4644 uint32_t
4646 {
4647 ChainStateTrack& st = srcState();
4648 jtx::Account const& srcdoor = srcDoor();
4649
4650 st.env
4653 .close(); // needed for claim_id sequence to be correct'
4654 st.spendFee(cr.from);
4655 st.transfer(cr.from, srcdoor, cr.amt);
4656 st.transfer(cr.from, srcdoor, cr.reward);
4657
4658 return ++st.counters[&bridge_].create_count;
4659 }
4660
4661 void
4662 attest(uint64_t time, uint32_t rnd)
4663 {
4664 ChainStateTrack& st = destState();
4665
4666 // check all signers, but start at a random one
4667 size_t i;
4668 for (i = 0; i < num_signers; ++i)
4669 {
4670 size_t signer_idx = (rnd + i) % num_signers;
4671
4672 if (!(cr.attested[signer_idx]))
4673 {
4674 // enqueue one attestation for this signer
4675 cr.attested[signer_idx] = true;
4676
4677 st.signers_attns[signer_idx][&bridge_]
4678 .create_claims[cr.claim_id - 1]
4679 .emplace_back(create_account_attestation(
4680 bridge_.signers[signer_idx].account,
4681 bridge_.jvb,
4682 cr.from,
4683 cr.amt,
4684 cr.reward,
4685 bridge_.signers[signer_idx].account,
4686 cr.a2b,
4687 cr.claim_id,
4688 cr.to,
4689 bridge_.signers[signer_idx]));
4690 break;
4691 }
4692 }
4693
4694 if (i == num_signers)
4695 return; // did not attest
4696
4697 auto& counters = st.counters[&bridge_];
4698 if (counters.create_callbacks.size() < cr.claim_id)
4699 counters.create_callbacks.resize(cr.claim_id);
4700
4701 auto complete_cb = [&](std::vector<size_t> const& signers) {
4702 auto num_attestors = signers.size();
4703 st.env.close();
4704 assert(
4705 num_attestors <=
4706 std::count(cr.attested.begin(), cr.attested.end(), true));
4707 assert(num_attestors >= bridge_.quorum);
4708 assert(cr.claim_id - 1 == counters.claim_count);
4709
4710 auto r = cr.reward;
4711 auto reward = divide(r, STAmount(num_attestors), r.issue());
4712
4713 for (auto i : signers)
4714 st.receive(bridge_.signers[i].account, reward);
4715
4716 st.spend(dstDoor(), reward, num_attestors);
4717 st.transfer(dstDoor(), cr.to, cr.amt);
4718 st.env.env_.memoize(cr.to);
4720 };
4721
4722 counters.create_callbacks[cr.claim_id - 1] = std::move(complete_cb);
4723 }
4724
4725 SmState
4726 advance(uint64_t time, uint32_t rnd)
4727 {
4728 switch (sm_state)
4729 {
4730 case st_initial:
4733 break;
4734
4735 case st_attesting:
4736 attest(time, rnd);
4737 break;
4738
4739 default:
4740 assert(0);
4741 break;
4742
4743 case st_completed:
4744 break; // will get this once
4745 }
4746 return sm_state;
4747 }
4748
4749 private:
4752 };
4753
4754 // --------------------------------------------------
4755 class SmTransfer : public SmBase<SmTransfer>
4756 {
4757 public:
4759
4761 const std::shared_ptr<ChainStateTracker>& chainstate,
4762 const BridgeDef& bridge,
4763 Transfer xfer)
4764 : Base(chainstate, bridge)
4765 , xfer(std::move(xfer))
4767 {
4768 }
4769
4770 bool
4771 a2b() const
4772 {
4773 return xfer.a2b;
4774 }
4775
4776 uint32_t
4778 {
4779 ChainStateTrack& st = destState();
4780
4781 st.env
4784 .close(); // needed for claim_id sequence to be
4785 // correct'
4786 st.spendFee(xfer.to);
4787 return ++st.counters[&bridge_].claim_id;
4788 }
4789
4790 void
4792 {
4793 ChainStateTrack& st = srcState();
4794 jtx::Account const& srcdoor = srcDoor();
4795
4796 if (xfer.amt.issue() != xrpIssue())
4797 {
4798 st.env.tx(pay(srcdoor, xfer.from, xfer.amt));
4799 st.spendFee(srcdoor);
4800 }
4801 st.env.tx(xchain_commit(
4802 xfer.from,
4803 bridge_.jvb,
4804 xfer.claim_id,
4805 xfer.amt,
4807 ? std::nullopt
4809 st.spendFee(xfer.from);
4810 st.transfer(xfer.from, srcdoor, xfer.amt);
4811 }
4812
4813 void
4815 {
4816 auto r = bridge_.reward;
4817 auto reward = divide(r, STAmount(bridge_.quorum), r.issue());
4818
4819 for (size_t i = 0; i < num_signers; ++i)
4820 {
4821 if (xfer.attested[i])
4822 st.receive(bridge_.signers[i].account, reward);
4823 }
4825 }
4826
4827 bool
4828 attest(uint64_t time, uint32_t rnd)
4829 {
4830 ChainStateTrack& st = destState();
4831
4832 // check all signers, but start at a random one
4833 for (size_t i = 0; i < num_signers; ++i)
4834 {
4835 size_t signer_idx = (rnd + i) % num_signers;
4836 if (!(xfer.attested[signer_idx]))
4837 {
4838 // enqueue one attestation for this signer
4839 xfer.attested[signer_idx] = true;
4840
4841 st.signers_attns[signer_idx][&bridge_]
4842 .xfer_claims.emplace_back(claim_attestation(
4843 bridge_.signers[signer_idx].account,
4844 bridge_.jvb,
4845 xfer.from,
4846 xfer.amt,
4847 bridge_.signers[signer_idx].account,
4848 xfer.a2b,
4849 xfer.claim_id,
4851 ? std::nullopt
4853 bridge_.signers[signer_idx]));
4854 break;
4855 }
4856 }
4857
4858 // return true if quorum was reached, false otherwise
4859 bool quorum =
4863 {
4866 }
4867 return quorum;
4868 }
4869
4870 void
4872 {
4873 ChainStateTrack& st = destState();
4874 st.env.tx(xchain_claim(
4878 st.spendFee(xfer.to);
4879 }
4880
4881 SmState
4882 advance(uint64_t time, uint32_t rnd)
4883 {
4884 switch (sm_state)
4885 {
4886 case st_initial:
4889 break;
4890
4891 case st_claimid_created:
4892 commit();
4894 break;
4895
4896 case st_attesting:
4897 sm_state = attest(time, rnd)
4899 : st_completed)
4900 : st_attesting;
4901 break;
4902
4903 case st_attested:
4904 assert(xfer.with_claim == WithClaim::yes);
4905 claim();
4907 break;
4908
4909 default:
4910 case st_completed:
4911 assert(0); // should have been removed
4912 break;
4913 }
4914 return sm_state;
4915 }
4916
4917 private:
4920 };
4921
4922 // --------------------------------------------------
4925
4927
4928 void
4930 uint64_t time,
4931 const std::shared_ptr<ChainStateTracker>& chainstate,
4932 BridgeDef const& bridge,
4933 Transfer transfer)
4934 {
4936 time, SmTransfer(chainstate, bridge, std::move(transfer)));
4937 }
4938
4939 void
4940 ac(uint64_t time,
4941 const std::shared_ptr<ChainStateTracker>& chainstate,
4942 BridgeDef const& bridge,
4944 {
4946 time, SmCreateAccount(chainstate, bridge, std::move(ac)));
4947 }
4948
4949public:
4950 void
4953 bool verify_balances = true)
4954 {
4955 using namespace jtx;
4956 uint64_t time = 0;
4957 std::mt19937 gen(27); // Standard mersenne_twister_engine
4959
4960 while (!sm_.empty())
4961 {
4962 ++time;
4963 for (auto it = sm_.begin(); it != sm_.end();)
4964 {
4965 auto vis = [&](auto& sm) {
4966 uint32_t rnd = distrib(gen);
4967 return sm.advance(time, rnd);
4968 };
4969 auto& [t, sm] = *it;
4970 if (t <= time && std::visit(vis, sm) == st_completed)
4971 it = sm_.erase(it);
4972 else
4973 ++it;
4974 }
4975
4976 // send attestations
4977 st->sendAttestations();
4978
4979 // make sure all transactions have been applied
4980 st->a_.env.close();
4981 st->b_.env.close();
4982
4983 if (verify_balances)
4984 {
4985 BEAST_EXPECT(st->verify());
4986 }
4987 }
4988 }
4989
4990 void
4992 {
4993 using namespace jtx;
4994
4995 testcase("Bridge usage simulation");
4996
4997 XEnv mcEnv(*this);
4998 XEnv scEnv(*this, true);
4999
5000 auto st = std::make_shared<ChainStateTracker>(mcEnv, scEnv);
5001
5002 // create 10 accounts + door funded on both chains, and store
5003 // in ChainStateTracker the initial amount of these accounts
5004 Account doorXRPLocking("doorXRPLocking"),
5005 doorUSDLocking("doorUSDLocking"), doorUSDIssuing("doorUSDIssuing");
5006
5007 constexpr size_t num_acct = 10;
5008 auto a = [&doorXRPLocking, &doorUSDLocking, &doorUSDIssuing]() {
5009 using namespace std::literals;
5010 std::vector<Account> result;
5011 result.reserve(num_acct);
5012 for (int i = 0; i < num_acct; ++i)
5013 result.emplace_back(
5014 "a"s + std::to_string(i),
5016 result.emplace_back("doorXRPLocking");
5017 doorXRPLocking = result.back();
5018 result.emplace_back("doorUSDLocking");
5019 doorUSDLocking = result.back();
5020 result.emplace_back("doorUSDIssuing");
5021 doorUSDIssuing = result.back();
5022 return result;
5023 }();
5024
5025 for (auto& acct : a)
5026 {
5027 STAmount amt{XRP(100000)};
5028
5029 mcEnv.fund(amt, acct);
5030 scEnv.fund(amt, acct);
5031 }
5032 Account USDLocking{"USDLocking"};
5033 IOU usdLocking{USDLocking["USD"]};
5034 IOU usdIssuing{doorUSDIssuing["USD"]};
5035
5036 mcEnv.fund(XRP(100000), USDLocking);
5037 mcEnv.close();
5038 mcEnv.tx(trust(doorUSDLocking, usdLocking(100000)));
5039 mcEnv.close();
5040 mcEnv.tx(pay(USDLocking, doorUSDLocking, usdLocking(50000)));
5041
5042 for (int i = 0; i < a.size(); ++i)
5043 {
5044 auto& acct{a[i]};
5045 if (i < num_acct)
5046 {
5047 mcEnv.tx(trust(acct, usdLocking(100000)));
5048 scEnv.tx(trust(acct, usdIssuing(100000)));
5049 }
5050 st->init(acct);
5051 }
5052 for (auto& s : signers)
5053 st->init(s.account);
5054
5055 st->b_.init(Account::master);
5056
5057 // also create some unfunded accounts
5058 constexpr size_t num_ua = 20;
5059 auto ua = []() {
5060 using namespace std::literals;
5061 std::vector<Account> result;
5062 result.reserve(num_ua);
5063 for (int i = 0; i < num_ua; ++i)
5064 result.emplace_back(
5065 "ua"s + std::to_string(i),
5067 return result;
5068 }();
5069
5070 // initialize a bridge from a BridgeDef
5071 auto initBridge = [&mcEnv, &scEnv, &st](BridgeDef& bd) {
5072 bd.initBridge(mcEnv, scEnv);
5073 st->a_.spendFee(bd.doorA, 2);
5074 st->b_.spendFee(bd.doorB, 2);
5075 };
5076
5077 // create XRP -> XRP bridge
5078 // ------------------------
5079 BridgeDef xrp_b{
5080 doorXRPLocking,
5081 xrpIssue(),
5083 xrpIssue(),
5084 XRP(1),
5085 XRP(20),
5086 quorum,
5087 signers,
5089
5090 initBridge(xrp_b);
5091
5092 // create USD -> USD bridge
5093 // ------------------------
5094 BridgeDef usd_b{
5095 doorUSDLocking,
5096 usdLocking,
5097 doorUSDIssuing,
5098 usdIssuing,
5099 XRP(1),
5100 XRP(20),
5101 quorum,
5102 signers,
5104
5105 initBridge(usd_b);
5106
5107 // try a single account create + transfer to validate the simulation
5108 // engine. Do the transfer 8 time steps after the account create, to
5109 // give time enough for ua[0] to be funded now so it can reserve
5110 // the claimID
5111 // -----------------------------------------------------------------
5112 ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, true});
5113 xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), true});
5114 runSimulation(st);
5115
5116 // try the same thing in the other direction
5117 // -----------------------------------------
5118 ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, false});
5119 xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), false});
5120 runSimulation(st);
5121
5122 // run multiple XRP transfers
5123 // --------------------------
5124 xfer(0, st, xrp_b, {a[0], a[0], a[1], XRP(6), true, WithClaim::no});
5125 xfer(1, st, xrp_b, {a[0], a[0], a[1], XRP(8), false, WithClaim::no});
5126 xfer(1, st, xrp_b, {a[1], a[1], a[1], XRP(1), true});
5127 xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(3), false});
5128 xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(5), false});
5129 xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(7), false, WithClaim::no});
5130 xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(9), true});
5131 runSimulation(st);
5132
5133 // run one USD transfer
5134 // --------------------
5135 xfer(0, st, usd_b, {a[0], a[1], a[2], usdLocking(3), true});
5136 runSimulation(st);
5137
5138 // run multiple USD transfers
5139 // --------------------------
5140 xfer(0, st, usd_b, {a[0], a[0], a[1], usdLocking(6), true});
5141 xfer(1, st, usd_b, {a[0], a[0], a[1], usdIssuing(8), false});
5142 xfer(1, st, usd_b, {a[1], a[1], a[1], usdLocking(1), true});
5143 xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(3), false});
5144 xfer(2, st, usd_b, {a[1], a[1], a[1], usdIssuing(5), false});
5145 xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(7), false});
5146 xfer(2, st, usd_b, {a[1], a[1], a[1], usdLocking(9), true});
5147 runSimulation(st);
5148
5149 // run mixed transfers
5150 // -------------------
5151 xfer(0, st, xrp_b, {a[0], a[0], a[0], XRP(1), true});
5152 xfer(0, st, usd_b, {a[1], a[3], a[3], usdIssuing(3), false});
5153 xfer(0, st, usd_b, {a[3], a[2], a[1], usdIssuing(5), false});
5154
5155 xfer(1, st, xrp_b, {a[0], a[0], a[0], XRP(4), false});
5156 xfer(1, st, xrp_b, {a[1], a[1], a[0], XRP(8), true});
5157 xfer(1, st, usd_b, {a[4], a[1], a[1], usdLocking(7), true});
5158
5159 xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(7), true});
5160 xfer(3, st, xrp_b, {a[0], a[4], a[3], XRP(2), false});
5161 xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(9), true});
5162 xfer(3, st, usd_b, {a[3], a[1], a[1], usdIssuing(11), false});
5163 runSimulation(st);
5164
5165 // run multiple account create to stress attestation batching
5166 // ----------------------------------------------------------
5167 ac(0, st, xrp_b, {a[0], ua[1], XRP(301), xrp_b.reward, true});
5168 ac(0, st, xrp_b, {a[1], ua[2], XRP(302), xrp_b.reward, true});
5169 ac(1, st, xrp_b, {a[0], ua[3], XRP(303), xrp_b.reward, true});
5170 ac(2, st, xrp_b, {a[1], ua[4], XRP(304), xrp_b.reward, true});
5171 ac(3, st, xrp_b, {a[0], ua[5], XRP(305), xrp_b.reward, true});
5172 ac(4, st, xrp_b, {a[1], ua[6], XRP(306), xrp_b.reward, true});
5173 ac(6, st, xrp_b, {a[0], ua[7], XRP(307), xrp_b.reward, true});
5174 ac(7, st, xrp_b, {a[2], ua[8], XRP(308), xrp_b.reward, true});
5175 ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true});
5176 ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true});
5177 ac(10, st, xrp_b, {a[0], ua[10], XRP(310), xrp_b.reward, true});
5178 ac(12, st, xrp_b, {a[0], ua[11], XRP(311), xrp_b.reward, true});
5179 ac(12, st, xrp_b, {a[3], ua[12], XRP(312), xrp_b.reward, true});
5180 ac(12, st, xrp_b, {a[4], ua[13], XRP(313), xrp_b.reward, true});
5181 ac(12, st, xrp_b, {a[3], ua[14], XRP(314), xrp_b.reward, true});
5182 ac(12, st, xrp_b, {a[6], ua[15], XRP(315), xrp_b.reward, true});
5183 ac(13, st, xrp_b, {a[7], ua[16], XRP(316), xrp_b.reward, true});
5184 ac(15, st, xrp_b, {a[3], ua[17], XRP(317), xrp_b.reward, true});
5185 runSimulation(st, true); // balances verification working now.
5186 }
5187
5188 void
5189 run() override
5190 {
5192 }
5193};
5194
5195BEAST_DEFINE_TESTSUITE(XChain, app, ripple);
5196BEAST_DEFINE_TESTSUITE(XChainSim, app, ripple);
5197
5198} // 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:580
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:589
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)
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:225
@ 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)