xrpld
Loading...
Searching...
No Matches
XChain_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/acctdelete.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/attester.h>
6#include <test/jtx/envconfig.h>
7#include <test/jtx/fee.h>
8#include <test/jtx/flags.h>
9#include <test/jtx/multisign.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/regkey.h>
12#include <test/jtx/ter.h>
13#include <test/jtx/trust.h>
14#include <test/jtx/txflags.h>
15#include <test/jtx/xchain_bridge.h>
16
17#include <xrpld/core/Config.h>
18
19#include <xrpl/basics/Log.h>
20#include <xrpl/basics/base_uint.h>
21#include <xrpl/beast/unit_test/suite.h>
22#include <xrpl/beast/utility/Journal.h>
23#include <xrpl/core/ServiceRegistry.h>
24#include <xrpl/json/json_value.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/Issue.h>
28#include <xrpl/protocol/KeyType.h>
29#include <xrpl/protocol/SField.h>
30#include <xrpl/protocol/STAmount.h>
31#include <xrpl/protocol/STXChainBridge.h>
32#include <xrpl/protocol/TER.h>
33#include <xrpl/protocol/TxFlags.h>
34#include <xrpl/protocol/XRPAmount.h>
35
36#include <algorithm>
37#include <array>
38#include <cassert>
39#include <cstddef>
40#include <cstdint>
41#include <exception>
42#include <functional>
43#include <list>
44#include <map>
45#include <memory>
46#include <optional>
47#include <random>
48#include <string>
49#include <tuple>
50#include <unordered_map>
51#include <utility>
52#include <variant>
53#include <vector>
54
55namespace xrpl::test {
56
57// SEnv class - encapsulate jtx::Env to make it more user-friendly,
58// for example having APIs that return a *this reference so that calls can be
59// chained (fluent interface) allowing to create an environment and use it
60// without encapsulating it in a curly brace block.
61// ---------------------------------------------------------------------------
62template <class T>
63struct SEnv
64{
66
68 T& s,
70 FeatureBitset features,
71 std::unique_ptr<Logs> logs = nullptr,
73 : env(s, std::move(config), features, std::move(logs), thresh)
74 {
75 }
76
77 SEnv&
79 {
80 env.close();
81 return *this;
82 }
83
84 SEnv&
85 enableFeature(uint256 const feature)
86 {
87 env.enableFeature(feature);
88 return *this;
89 }
90
91 SEnv&
92 disableFeature(uint256 const feature)
93 {
94 env.app().config().features.erase(feature);
95 return *this;
96 }
97
98 template <class Arg, class... Args>
99 SEnv&
100 fund(STAmount const& amount, Arg const& arg, Args const&... args)
101 {
102 env.fund(amount, arg, args...);
103 return *this;
104 }
105
106 template <class JsonValue, class... FN>
107 SEnv&
108 tx(JsonValue&& jv, FN const&... fN)
109 {
110 env(std::forward<JsonValue>(jv), fN...);
111 return *this;
112 }
113
114 template <class... FN>
115 SEnv&
116 multiTx(jtx::JValueVec const& jvv, FN const&... fN)
117 {
118 for (auto const& jv : jvv)
119 env(jv, fN...);
120 return *this;
121 }
122
123 [[nodiscard]] TER
124 ter() const
125 {
126 return env.ter();
127 }
128
129 [[nodiscard]] STAmount
131 {
132 return env.balance(account).value();
133 }
134
135 [[nodiscard]] STAmount
136 balance(jtx::Account const& account, Issue const& issue) const
137 {
138 return env.balance(account, issue).value();
139 }
140
143 {
144 return env.current()->fees().accountReserve(count);
145 }
146
149 {
150 return env.current()->fees().base;
151 }
152
155 {
156 return env.le(account);
157 }
158
160 bridge(json::Value const& jvb)
161 {
162 STXChainBridge const b(jvb);
163
164 auto tryGet = [&](STXChainBridge::ChainType ct) -> SLE::const_pointer {
165 if (auto r = env.le(keylet::bridge(b, ct)))
166 {
167 if ((*r)[sfXChainBridge] == b)
168 return r;
169 }
170 return nullptr;
171 };
172 if (auto r = tryGet(STXChainBridge::ChainType::Locking))
173 return r;
175 }
176
179 {
180 return (*bridge(jvb))[sfXChainAccountClaimCount];
181 }
182
185 {
186 return (*bridge(jvb))[sfXChainClaimID];
187 }
188
191 {
192 return env.le(keylet::xChainClaimID(STXChainBridge(jvb), seq));
193 }
194
197 {
199 }
200};
201
202// XEnv class used for XChain tests. The only difference with SEnv<T> is that it
203// funds some default accounts, and that it enables `testable_amendments() |
204// FeatureBitset{featureXChainBridge}` by default.
205// -----------------------------------------------------------------------------
206template <class T>
207struct XEnv : public jtx::XChainBridgeObjects, public SEnv<T>
208{
209 XEnv(T& s, bool side = false) : SEnv<T>(s, jtx::envconfig(), features)
210 {
211 using namespace jtx;
212 STAmount const xrpFunds{XRP(10000)};
213
214 if (!side)
215 {
216 this->fund(xrpFunds, mcDoor, mcAlice, mcBob, mcCarol, mcGw);
217
218 // Signer's list must match the attestation signers
219 // env_(jtx::signers(mcDoor, quorum, signers));
220 for (auto& s : signers)
221 this->fund(xrpFunds, s.account);
222 }
223 else
224 {
225 this->fund(xrpFunds, scDoor, scAlice, scBob, scCarol, scGw, scAttester, scReward);
226
227 for (auto& ra : payees)
228 this->fund(xrpFunds, ra);
229
230 for (auto& s : signers)
231 this->fund(xrpFunds, s.account);
232
233 // Signer's list must match the attestation signers
234 // env_(jtx::signers(Account::kMaster, quorum, signers));
235 }
236 this->close();
237 }
238};
239
240// Tracks the xrp balance for one account
241template <class T>
243{
245 T& env;
247
249 {
250 startAmount = env.balance(account);
251 }
252
253 [[nodiscard]] STAmount
254 diff() const
255 {
256 return env.balance(account) - startAmount;
257 }
258};
259
260// Tracks the xrp balance for multiple accounts involved in a crosss-chain
261// transfer
262template <class T>
264{
266
269 balance payer; // pays the rewards
270 std::vector<balance> rewardAccounts; // receives the reward
272
274 T& env,
275 jtx::Account const& fromAcct,
276 jtx::Account const& toAcct,
277 jtx::Account const& payer,
278 jtx::Account const* payees,
279 size_t numPayees,
280 bool withClaim)
281 : from(env, fromAcct)
282 , to(env, toAcct)
283 , payer(env, payer)
284 , rewardAccounts([&]() {
286 r.reserve(numPayees);
287 for (size_t i = 0; i < numPayees; ++i)
288 r.emplace_back(env, payees[i]);
289 return r;
290 }())
291 , txFees(withClaim ? env.env.current()->fees().base : XRPAmount(0))
292 {
293 }
294
296 T& env,
297 jtx::Account const& fromAcct,
298 jtx::Account const& toAcct,
299 jtx::Account const& payer,
300 std::vector<jtx::Account> const& payees,
301 bool withClaim)
302 : BalanceTransfer(env, fromAcct, toAcct, payer, &payees[0], payees.size(), withClaim)
303 {
304 }
305
306 [[nodiscard]] bool
307 payeesReceived(STAmount const& reward) const
308 {
309 return std::ranges::all_of(
310 rewardAccounts, [&](balance const& b) { return b.diff() == reward; });
311 }
312
313 bool
314 checkMostBalances(STAmount const& amt, STAmount const& reward)
315 {
316 return from.diff() == -amt && to.diff() == amt && payeesReceived(reward);
317 }
318
319 bool
320 hasHappened(STAmount const& amt, STAmount const& reward, bool checkPayer = true)
321 {
322 auto rewardCost = multiply(reward, STAmount(rewardAccounts.size()), reward.asset());
323 return checkMostBalances(amt, reward) &&
324 (!checkPayer || payer.diff() == -(rewardCost + txFees));
325 }
326
327 bool
329 {
330 return checkMostBalances(STAmount(0), STAmount(0)) &&
331 payer.diff() <= txFees; // could have paid fee for failed claim
332 }
333};
334
336{
343 uint32_t quorum;
346
347 template <class ENV>
348 void
349 initBridge(ENV& mcEnv, ENV& scEnv)
350 {
352
353 auto const optAccountCreate = [&]() -> std::optional<STAmount> {
354 if (issueA != xrpIssue() || issueB != xrpIssue())
355 return {};
356 return minAccountCreate;
357 }();
358 mcEnv.tx(bridgeCreate(doorA, jvb, reward, optAccountCreate))
360 .close();
361
362 scEnv.tx(bridgeCreate(doorB, jvb, reward, optAccountCreate))
364 .close();
365 }
366};
367
369{
372 {
373 return XEnv(*this).env.current()->fees().accountReserve(count);
374 }
375
378 {
379 return XEnv(*this).env.current()->fees().base;
380 }
381
382 void
384 {
385 auto jBridge = createBridge(mcDoor)[sfXChainBridge.jsonName];
386 bool exceptionPresent = false;
387 try
388 {
389 exceptionPresent = false;
390 [[maybe_unused]] STXChainBridge const testBridge1(jBridge);
391 }
392 catch (std::exception& ec)
393 {
394 exceptionPresent = true;
395 }
396
397 BEAST_EXPECT(!exceptionPresent);
398
399 try
400 {
401 exceptionPresent = false;
402 jBridge["Extra"] = 1;
403 [[maybe_unused]] STXChainBridge const testBridge2(jBridge);
404 }
405 catch ([[maybe_unused]] std::exception& ec)
406 {
407 exceptionPresent = true;
408 }
409
410 BEAST_EXPECT(exceptionPresent);
411 }
412
413 void
415 {
416 XRPAmount const res1 = reserve(1);
417
418 using namespace jtx;
419 testcase("Create Bridge");
420
421 // Normal create_bridge => should succeed
422 XEnv(*this).tx(createBridge(mcDoor)).close();
423
424 // Bridge not owned by one of the door account.
426
427 // Create twice on the same account
429
430 // Create USD bridge Alice -> Bob ... should succeed
431 XEnv(*this).tx(
432 createBridge(mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])),
433 Ter(tesSUCCESS));
434
435 // Create USD bridge, Alice is both the locking door and locking issue,
436 // ... should fail.
437 XEnv(*this).tx(
440
441 // Bridge where the two door accounts are equal.
442 XEnv(*this).tx(
443 createBridge(mcBob, bridge(mcBob, mcGw["USD"], mcBob, mcGw["USD"])),
445
446 // Both door accounts are on the same chain. This is not allowed.
447 // Although it doesn't violate any invariants, it's not a useful thing
448 // to do and it complicates the "add claim" transactions.
449 XEnv(*this)
450 .tx(createBridge(mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])))
451 .close()
452 .tx(createBridge(mcBob, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"])),
454 .close();
455
456 // Create a bridge on an account with exactly enough balance to
457 // meet the new reserve should succeed
458 XEnv(*this)
459 .fund(res1, mcuDoor) // exact reserve for account + 1 object
460 .close()
462
463 // Create a bridge on an account with no enough balance to meet the
464 // new reserve
465 XEnv(*this)
466 .fund(res1 - 1, mcuDoor) // just short of required reserve
467 .close()
469
470 // Reward amount is non-xrp
471 XEnv(*this).tx(
473
474 // Reward amount is XRP and negative
476
477 // Reward amount is 1 xrp => should succeed
478 XEnv(*this).tx(createBridge(mcDoor, jvb, XRP(1)), Ter(tesSUCCESS));
479
480 // Min create amount is 1 xrp, mincreate is 1 xrp => should succeed
481 XEnv(*this).tx(createBridge(mcDoor, jvb, XRP(1), XRP(1)), Ter(tesSUCCESS));
482
483 // Min create amount is non-xrp
484 XEnv(*this).tx(
485 createBridge(mcDoor, jvb, XRP(1), mcUSD(100)),
487
488 // Min create amount is zero (should fail, currently succeeds)
489 XEnv(*this).tx(
490 createBridge(mcDoor, jvb, XRP(1), XRP(0)),
492
493 // Min create amount is negative
494 XEnv(*this).tx(
495 createBridge(mcDoor, jvb, XRP(1), XRP(-1)),
497
498 // coverage test: BridgeCreate::preflight() - create bridge when feature
499 // disabled.
500 {
501 Env env(*this, testableAmendments() - featureXChainBridge);
503 }
504
505 // coverage test: BridgeCreate::preclaim() returns tecNO_ISSUER.
506 XEnv(*this).tx(
509
510 // coverage test: create_bridge transaction with incorrect flag
511 XEnv(*this).tx(createBridge(mcAlice, jvb), Txflags(tfFillOrKill), Ter(temINVALID_FLAG));
512
513 // coverage test: create_bridge transaction with xchain feature disabled
514 XEnv(*this)
515 .disableFeature(featureXChainBridge)
517 }
518
519 void
521 {
566
567 using namespace jtx;
568 testcase("Bridge create constraints");
569 XEnv env(*this, true);
570 auto& a = scAlice;
571 auto& b = scBob;
572 auto& c = scCarol;
573 auto ausd = a["USD"];
574 auto busd = b["USD"];
575 auto cusd = c["USD"];
576 auto gusd = scGw["USD"];
577 auto aeur = a["EUR"];
578 auto beur = b["EUR"];
579 auto ceur = c["EUR"];
580 auto geur = scGw["EUR"];
581
582 // Accounts to own single bridges
583 Account const a1("a1");
584 Account const a2("a2");
585 Account const a3("a3");
586 Account const a4("a4");
587 Account const a5("a5");
588 Account const a6("a6");
589
590 env.fund(XRP(10000), a1, a2, a3, a4, a5, a6);
591 env.close();
592
593 // Add a bridge on two different accounts with the same locking and
594 // issuing assets
595 env.tx(createBridge(a1, bridge(a1, gusd, b, busd))).close();
596 env.tx(createBridge(a2, bridge(a2, gusd, b, busd))).close();
597
598 // Add the exact same bridge to two different accounts (one locking
599 // account and one issuing)
600 env.tx(createBridge(a3, bridge(a3, gusd, a4, a4["USD"]))).close();
601 env.tx(createBridge(a4, bridge(a3, gusd, a4, a4["USD"])), Ter(tecDUPLICATE)).close();
602
603 // Add the exact same bridge to two different accounts (one issuing
604 // account and one locking - opposite order from the test above)
605 env.tx(createBridge(a5, bridge(a6, gusd, a5, a5["USD"]))).close();
606 env.tx(createBridge(a6, bridge(a6, gusd, a5, a5["USD"])), Ter(tecDUPLICATE)).close();
607
608 // Test case 1 ~ 5, create bridges
609 auto const goodBridge1 = bridge(a, gusd, b, busd);
610 auto const goodBridge2 = bridge(a, busd, c, cusd);
611 env.tx(createBridge(b, goodBridge1)).close();
612 // Issuing asset is the same, this is a duplicate
613 env.tx(createBridge(b, bridge(a, geur, b, busd)), Ter(tecDUPLICATE)).close();
614 env.tx(createBridge(a, goodBridge2), Ter(tesSUCCESS)).close();
615 // Locking asset is the same - this is a duplicate
616 env.tx(createBridge(a, bridge(a, busd, b, beur)), Ter(tecDUPLICATE)).close();
617 // Locking asset is USD - this is a duplicate even tho it has a
618 // different issuer
619 env.tx(createBridge(a, bridge(a, cusd, b, beur)), Ter(tecDUPLICATE)).close();
620
621 // Test case 6 and 7, commits
622 env.tx(trust(c, busd(1000)))
623 .tx(trust(a, busd(1000)))
624 .close()
625 .tx(pay(b, c, busd(1000)))
626 .close();
627 auto const aBalanceStart = env.balance(a, busd);
628 auto const cBalanceStart = env.balance(c, busd);
629 env.tx(xchainCommit(c, goodBridge1, 1, busd(50))).close();
630 BEAST_EXPECT(env.balance(a, busd) - aBalanceStart == busd(0));
631 BEAST_EXPECT(env.balance(c, busd) - cBalanceStart == busd(-50));
632 env.tx(xchainCommit(c, goodBridge2, 1, busd(60))).close();
633 BEAST_EXPECT(env.balance(a, busd) - aBalanceStart == busd(60));
634 BEAST_EXPECT(env.balance(c, busd) - cBalanceStart == busd(-50 - 60));
635
636 // bridge modify test cases
637 env.tx(bridgeModify(b, goodBridge1, XRP(33), std::nullopt)).close();
638 BEAST_EXPECT((*env.bridge(goodBridge1))[sfSignatureReward] == XRP(33));
639 env.tx(bridgeModify(a, goodBridge2, XRP(44), std::nullopt)).close();
640 BEAST_EXPECT((*env.bridge(goodBridge2))[sfSignatureReward] == XRP(44));
641 }
642
643 void
645 {
646 using namespace jtx;
647 testcase("Create Bridge Matrix");
648
649 // Test all combinations of the following:`
650 // --------------------------------------
651 // - Locking chain is IOU with locking chain door account as issuer
652 // - Locking chain is IOU with issuing chain door account that
653 // exists on the locking chain as issuer
654 // - Locking chain is IOU with issuing chain door account that does
655 // not exists on the locking chain as issuer
656 // - Locking chain is IOU with non-door account (that exists on the
657 // locking chain ledger) as issuer
658 // - Locking chain is IOU with non-door account (that does not exist
659 // exists on the locking chain ledger) as issuer
660 // - Locking chain is XRP
661 // ---------------------------------------------------------------------
662 // - Issuing chain is IOU with issuing chain door account as the
663 // issuer
664 // - Issuing chain is IOU with locking chain door account (that
665 // exists on the issuing chain ledger) as the issuer
666 // - Issuing chain is IOU with locking chain door account (that does
667 // not exist on the issuing chain ledger) as the issuer
668 // - Issuing chain is IOU with non-door account (that exists on the
669 // issuing chain ledger) as the issuer
670 // - Issuing chain is IOU with non-door account (that does not
671 // exists on the issuing chain ledger) as the issuer
672 // - Issuing chain is XRP and issuing chain door account is not the
673 // root account
674 // - Issuing chain is XRP and issuing chain door account is the root
675 // account
676 // ---------------------------------------------------------------------
677 // That's 42 combinations. The only combinations that should succeed
678 // are:
679 // - Locking chain is any IOU,
680 // - Issuing chain is IOU with issuing chain door account as the
681 // issuer
682 // Locking chain is XRP,
683 // - Issuing chain is XRP with issuing chain is the root account.
684 // ---------------------------------------------------------------------
685 Account a("a"), b("b");
686 Issue ia, ib;
687
688 std::tuple lcs{
690 "Locking chain is IOU(locking chain door)",
691 [&](auto& env, bool) {
692 a = mcDoor;
693 ia = mcDoor["USD"];
694 }),
696 "Locking chain is IOU(issuing chain door funded on locking "
697 "chain)",
698 [&](auto& env, bool shouldFund) {
699 a = mcDoor;
700 ia = scDoor["USD"];
701 if (shouldFund)
702 env.fund(XRP(10000), scDoor);
703 }),
705 "Locking chain is IOU(issuing chain door account unfunded "
706 "on locking chain)",
707 [&](auto& env, bool) {
708 a = mcDoor;
709 ia = scDoor["USD"];
710 }),
712 "Locking chain is IOU(bob funded on locking chain)",
713 [&](auto& env, bool) {
714 a = mcDoor;
715 ia = mcGw["USD"];
716 }),
718 "Locking chain is IOU(bob unfunded on locking chain)",
719 [&](auto& env, bool) {
720 a = mcDoor;
721 ia = mcuGw["USD"];
722 }),
723 std::make_pair("Locking chain is XRP", [&](auto& env, bool) {
724 a = mcDoor;
725 ia = xrpIssue();
726 })};
727
728 std::tuple ics{
730 "Issuing chain is IOU(issuing chain door account)",
731 [&](auto& env, bool) {
732 b = scDoor;
733 ib = scDoor["USD"];
734 }),
736 "Issuing chain is IOU(locking chain door funded on issuing "
737 "chain)",
738 [&](auto& env, bool shouldFund) {
739 b = scDoor;
740 ib = mcDoor["USD"];
741 if (shouldFund)
742 env.fund(XRP(10000), mcDoor);
743 }),
745 "Issuing chain is IOU(locking chain door unfunded on "
746 "issuing chain)",
747 [&](auto& env, bool) {
748 b = scDoor;
749 ib = mcDoor["USD"];
750 }),
752 "Issuing chain is IOU(bob funded on issuing chain)",
753 [&](auto& env, bool) {
754 b = scDoor;
755 ib = mcGw["USD"];
756 }),
758 "Issuing chain is IOU(bob unfunded on issuing chain)",
759 [&](auto& env, bool) {
760 b = scDoor;
761 ib = mcuGw["USD"];
762 }),
764 "Issuing chain is XRP and issuing chain door account is "
765 "not the root account",
766 [&](auto& env, bool) {
767 b = scDoor;
768 ib = xrpIssue();
769 }),
771 "Issuing chain is XRP and issuing chain door account is "
772 "the root account ",
773 [&](auto& env, bool) {
775 ib = xrpIssue();
776 })};
777
778 std::vector<std::pair<int, int>> expectedResult{
821
823
824 auto testcase = [&](auto const& lc, auto const& ic) {
825 XEnv mcEnv(*this);
826 XEnv scEnv(*this, true);
827
828 lc.second(mcEnv, true);
829 lc.second(scEnv, false);
830
831 ic.second(mcEnv, false);
832 ic.second(scEnv, true);
833
834 auto const& expected = expectedResult[testResult.size()];
835
836 mcEnv.tx(createBridge(a, bridge(a, ia, b, ib)), Ter(TER::fromInt(expected.first)));
837 TER const mcTER = mcEnv.env.ter();
838
839 scEnv.tx(createBridge(b, bridge(a, ia, b, ib)), Ter(TER::fromInt(expected.second)));
840 TER const scTER = scEnv.env.ter();
841
842 bool const pass = isTesSuccess(mcTER) && isTesSuccess(scTER);
843
844 testResult.emplace_back(mcTER, scTER, pass);
845 };
846
847 auto applyIcs = [&](auto const& lc, auto const& ics) {
848 std::apply([&](auto const&... ic) { (testcase(lc, ic), ...); }, ics);
849 };
850
851 std::apply([&](auto const&... lc) { (applyIcs(lc, ics), ...); }, lcs);
852
853#if GENERATE_MTX_OUTPUT
854 // optional output of matrix results in markdown format
855 // ----------------------------------------------------
856 std::string fname{std::tmpnam(nullptr)};
857 fname += ".md";
858 std::cout << "Markdown output for matrix test: " << fname << "\n";
859
860 auto print_res = [](auto tup) -> std::string {
861 std::string status =
862 std::string(transToken(std::get<0>(tup))) + " / " + transToken(std::get<1>(tup));
863
864 if (std::get<2>(tup))
865 return status;
866 else
867 {
868 // red
869 return std::string("`") + status + "`";
870 }
871 };
872
873 auto output_table = [&](auto print_res) {
874 size_t test_idx = 0;
875 std::string res;
876 res.reserve(10000); // should be enough :-)
877
878 // first two header lines
879 res += "| `issuing ->` | ";
880 std::apply([&](auto const&... ic) { ((res += ic.first, res += " | "), ...); }, ics);
881 res += "\n";
882
883 res += "| :--- | ";
885 [&](auto const&... ic) { (((void)ic.first, res += ":---: | "), ...); }, ics);
886 res += "\n";
887
888 auto output = [&](auto const& lc, auto const& ic) {
889 res += print_res(test_result[test_idx]);
890 res += " | ";
891 ++test_idx;
892 };
893
894 auto output_ics = [&](auto const& lc, auto const& ics) {
895 res += "| ";
896 res += lc.first;
897 res += " | ";
898 std::apply([&](auto const&... ic) { (output(lc, ic), ...); }, ics);
899 res += "\n";
900 };
901
902 std::apply([&](auto const&... lc) { (output_ics(lc, ics), ...); }, lcs);
903
904 return res;
905 };
906
907 std::ofstream(fname) << output_table(print_res);
908
909 std::string ter_fname{std::tmpnam(nullptr)};
910 std::cout << "ter output for matrix test: " << ter_fname << "\n";
911
912 std::ofstream ofs(ter_fname);
913 for (auto& t : test_result)
914 {
915 ofs << "{ " << std::string(transToken(std::get<0>(t))) << ", "
916 << std::string(transToken(std::get<1>(t))) << "}\n,";
917 }
918#endif
919 }
920
921 void
923 {
924 using namespace jtx;
925 testcase("Modify Bridge");
926
927 // Changing a non-existent bridge should fail
928 XEnv(*this).tx(
930 mcAlice, bridge(mcAlice, mcGw["USD"], mcBob, mcBob["USD"]), XRP(2), std::nullopt),
932
933 // must change something
934 // XEnv(*this)
935 // .tx(create_bridge(mcDoor, jvb, XRP(1), XRP(1)))
936 // .tx(bridge_modify(mcDoor, jvb, XRP(1), XRP(1)),
937 // Ter(temMALFORMED));
938
939 // must change something
940 XEnv(*this)
941 .tx(createBridge(mcDoor, jvb, XRP(1), XRP(1)))
942 .close()
944
945 // Reward amount is non-xrp
946 XEnv(*this).tx(
948
949 // Reward amount is XRP and negative
950 XEnv(*this).tx(
952
953 // Min create amount is non-xrp
954 XEnv(*this).tx(
955 bridgeModify(mcDoor, jvb, XRP(2), mcUSD(10)),
957
958 // Min create amount is zero
959 XEnv(*this).tx(
960 bridgeModify(mcDoor, jvb, XRP(2), XRP(0)),
962
963 // Min create amount is negative
964 XEnv(*this).tx(
965 bridgeModify(mcDoor, jvb, XRP(2), XRP(-10)),
967
968 // First check the regular claim process (without bridge_modify)
969 for (auto withClaim : {false, true})
970 {
971 XEnv mcEnv(*this);
972 XEnv scEnv(*this, true);
973
974 mcEnv.tx(createBridge(mcDoor, jvb)).close();
975
978 .close()
980 .close();
981
982 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
983 auto const amt = XRP(1000);
984 std::uint32_t const claimID = 1;
985 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
986
987 BalanceTransfer transfer(
988 scEnv,
990 scBob,
991 scAlice,
992 &payees[0],
994 withClaim);
995
996 scEnv
998 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers))
999 .close();
1000
1001 if (withClaim)
1002 {
1003 BEAST_EXPECT(transfer.hasNotHappened());
1004
1005 // need to submit a claim transactions
1006 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
1007 }
1008
1009 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum));
1010 }
1011
1012 // Check that the reward paid from a claim Id was the reward when
1013 // the claim id was created, not the reward since the bridge was
1014 // modified.
1015 for (auto withClaim : {false, true})
1016 {
1017 XEnv mcEnv(*this);
1018 XEnv scEnv(*this, true);
1019
1020 mcEnv.tx(createBridge(mcDoor, jvb)).close();
1021
1024 .close()
1026 .close();
1027
1028 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1029 auto const amt = XRP(1000);
1030 std::uint32_t const claimID = 1;
1031 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
1032
1033 // Now modify the reward on the bridge
1034 mcEnv.tx(bridgeModify(mcDoor, jvb, XRP(2), XRP(10))).close();
1035 scEnv.tx(bridgeModify(Account::kMaster, jvb, XRP(2), XRP(10))).close();
1036
1037 BalanceTransfer transfer(
1038 scEnv,
1040 scBob,
1041 scAlice,
1042 &payees[0],
1044 withClaim);
1045
1046 scEnv
1048 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers))
1049 .close();
1050
1051 if (withClaim)
1052 {
1053 BEAST_EXPECT(transfer.hasNotHappened());
1054
1055 // need to submit a claim transactions
1056 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
1057 }
1058
1059 // make sure the reward accounts indeed received the original
1060 // split reward (1 split 5 ways) instead of the updated 2 XRP.
1061 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum));
1062 }
1063
1064 // Check that the signatures used to verify attestations and decide
1065 // if there is a quorum are the current signer's list on the door
1066 // account, not the signer's list that was in effect when the claim
1067 // id was created.
1068 for (auto withClaim : {false, true})
1069 {
1070 XEnv mcEnv(*this);
1071 XEnv scEnv(*this, true);
1072
1073 mcEnv.tx(createBridge(mcDoor, jvb)).close();
1074
1077 .close()
1079 .close();
1080
1081 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1082 auto const amt = XRP(1000);
1083 std::uint32_t const claimID = 1;
1084 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
1085
1086 // change signers - claim should not be processed is the batch
1087 // is signed by original signers
1089
1090 BalanceTransfer transfer(
1091 scEnv,
1093 scBob,
1094 scAlice,
1095 &payees[0],
1097 withClaim);
1098
1099 // submit claim using outdated signers - should fail
1100 scEnv
1101 .multiTx(
1103 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers),
1105 .close();
1106 if (withClaim)
1107 {
1108 // need to submit a claim transactions
1109 scEnv
1110 .tx(xchainClaim(scAlice, jvb, claimID, amt, scBob),
1112 .close();
1113 }
1114
1115 // make sure transfer has not happened as we sent attestations
1116 // using outdated signers
1117 BEAST_EXPECT(transfer.hasNotHappened());
1118
1119 // submit claim using current signers - should succeed
1120 scEnv
1122 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, altSigners))
1123 .close();
1124 if (withClaim)
1125 {
1126 BEAST_EXPECT(transfer.hasNotHappened());
1127
1128 // need to submit a claim transactions
1129 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
1130 }
1131
1132 // make sure the transfer went through as we sent attestations
1133 // using new signers
1134 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum, false));
1135 }
1136
1137 // coverage test: bridge_modify transaction with incorrect flag
1138 XEnv(*this)
1140 .close()
1141 .tx(bridgeModify(mcDoor, jvb, XRP(1), XRP(2)),
1142 Txflags(tfFillOrKill),
1144
1145 // coverage test: bridge_modify transaction with xchain feature
1146 // disabled
1147 XEnv(*this)
1149 .disableFeature(featureXChainBridge)
1150 .close()
1152
1153 // coverage test: bridge_modify return temSIDECHAIN_NONDOOR_OWNER;
1154 XEnv(*this)
1156 .close()
1158
1165 XEnv(*this)
1166 .tx(createBridge(mcDoor, jvb, XRP(1), XRP(20)))
1167 .close()
1169 .close()
1170 .tx(bridgeModify(mcDoor, jvb, {}, XRP(2)),
1171 Txflags(tfClearAccountCreateAmount),
1173 .close()
1174 .tx(bridgeModify(mcDoor, jvb, XRP(3), {}), Txflags(tfClearAccountCreateAmount))
1175 .close()
1178 .close();
1179 }
1180
1181 void
1183 {
1184 using namespace jtx;
1185 XRPAmount const res1 = reserve(1);
1186 XRPAmount const fee = txFee();
1187
1188 testcase("Create ClaimID");
1189
1190 // normal bridge create for sanity check with the exact necessary
1191 // account balance
1192 XEnv(*this, true)
1194 .fund(res1, scuAlice) // acct reserve + 1 object
1195 .close()
1197 .close();
1198
1199 // check reward not deducted when claim id is created
1200 {
1201 XEnv xenv(*this, true);
1202
1203 test::Balance const scAliceBal(xenv, scAlice);
1204
1207 .close();
1208
1209 BEAST_EXPECT(scAliceBal.diff() == -fee);
1210 }
1211
1212 // Non-existent bridge
1213 XEnv(*this, true)
1215 scAlice, bridge(mcAlice, mcAlice["USD"], scBob, scBob["USD"]), reward, mcAlice),
1217 .close();
1218
1219 // Creating the new object would put the account below the reserve
1220 XEnv(*this, true)
1222 .fund(res1 - xrpDust, scuAlice) // barely not enough
1223 .close()
1225 .close();
1226
1227 // The specified reward doesn't match the reward on the bridge (test
1228 // by giving the reward amount for the other side, as well as a
1229 // completely non-matching reward)
1230 XEnv(*this, true)
1232 .close()
1235 .close();
1236
1237 // A reward amount that isn't XRP
1238 XEnv(*this, true)
1240 .close()
1243 .close();
1244
1245 // coverage test: xchain_create_claim_id transaction with incorrect
1246 // flag
1247 XEnv(*this, true)
1249 .close()
1251 Txflags(tfFillOrKill),
1253 .close();
1254
1255 // coverage test: xchain_create_claim_id transaction with xchain
1256 // feature disabled
1257 XEnv(*this, true)
1259 .disableFeature(featureXChainBridge)
1260 .close()
1262 .close();
1263 }
1264
1265 void
1267 {
1268 using namespace jtx;
1269 XRPAmount const res0 = reserve(0);
1270 XRPAmount const fee = txFee();
1271
1272 testcase("Commit");
1273
1274 // Commit to a non-existent bridge
1276
1277 // check that reward not deducted when doing the commit
1278 {
1279 XEnv xenv(*this);
1280
1281 test::Balance const aliceBal(xenv, mcAlice);
1282 auto const amt = XRP(1000);
1283
1284 xenv.tx(createBridge(mcDoor, jvb))
1285 .close()
1286 .tx(xchainCommit(mcAlice, jvb, 1, amt, scBob))
1287 .close();
1288
1289 STAmount const claimCost = amt;
1290 BEAST_EXPECT(aliceBal.diff() == -(claimCost + fee));
1291 }
1292
1293 // Commit a negative amount
1294 XEnv(*this)
1296 .close()
1298
1299 // Commit an amount whose issue that does not match the expected
1300 // issue on the bridge (either LockingChainIssue or
1301 // IssuingChainIssue, depending on the chain).
1302 XEnv(*this)
1304 .close()
1306
1307 // Commit an amount that would put the sender below the required
1308 // reserve (if XRP)
1309 XEnv(*this)
1311 .fund(res0 + oneXrp - xrpDust, mcuAlice) // barely not enough
1312 .close()
1314
1315 XEnv(*this)
1317 .fund(
1318 res0 + oneXrp + xrpDust, // "xrp_dust" for tx fees
1319 mcuAlice) // exactly enough => should succeed
1320 .close()
1322
1323 // Commit an amount above the account's balance (for both XRP and
1324 // IOUs)
1325 XEnv(*this)
1327 .fund(res0, mcuAlice) // barely not enough
1328 .close()
1330
1331 auto jvbUsd = bridge(mcDoor, mcUSD, scGw, scUSD);
1332
1333 // commit sent from iou issuer (mcGw) succeeds - should it?
1334 XEnv(*this)
1335 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1336 .tx(createBridge(mcDoor, jvbUsd))
1337 .close()
1338 .tx(xchainCommit(mcGw, jvbUsd, 1, mcUSD(1), scBob));
1339
1340 // commit to a door account from the door account. This should fail.
1341 XEnv(*this)
1342 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1343 .tx(createBridge(mcDoor, jvbUsd))
1344 .close()
1346
1347 // commit sent from mcAlice which has no IOU balance => should fail
1348 XEnv(*this)
1349 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1350 .tx(createBridge(mcDoor, jvbUsd))
1351 .close()
1352 .tx(xchainCommit(mcAlice, jvbUsd, 1, mcUSD(1), scBob), Ter(terNO_LINE));
1353
1354 // commit sent from mcAlice which has no IOU balance => should fail
1355 // just changed the destination to scGw (which is the door account
1356 // and may not make much sense)
1357 XEnv(*this)
1358 .tx(trust(mcDoor, mcUSD(10000))) // door needs to have a trustline
1359 .tx(createBridge(mcDoor, jvbUsd))
1360 .close()
1361 .tx(xchainCommit(mcAlice, jvbUsd, 1, mcUSD(1), scGw), Ter(terNO_LINE));
1362
1363 // commit sent from mcAlice which has a IOU balance => should
1364 // succeed
1365 XEnv(*this)
1366 .tx(trust(mcDoor, mcUSD(10000)))
1367 .tx(trust(mcAlice, mcUSD(10000)))
1368 .close()
1369 .tx(pay(mcGw, mcAlice, mcUSD(10)))
1370 .tx(createBridge(mcDoor, jvbUsd))
1371 .close()
1372 //.tx(pay(mcAlice, mcDoor, mcUSD(10)));
1373 .tx(xchainCommit(mcAlice, jvbUsd, 1, mcUSD(10), scAlice));
1374
1375 // coverage test: xchain_commit transaction with incorrect flag
1376 XEnv(*this)
1378 .close()
1380 Txflags(tfFillOrKill),
1382
1383 // coverage test: xchain_commit transaction with xchain feature
1384 // disabled
1385 XEnv(*this)
1387 .disableFeature(featureXChainBridge)
1388 .close()
1390 }
1391
1392 void
1394 {
1395 using namespace jtx;
1396
1397 testcase("Add Attestation");
1398 XRPAmount const res0 = reserve(0);
1399 XRPAmount fee = txFee();
1400
1401 auto multiTtxFee = [&](std::uint32_t m) -> STAmount {
1402 return multiply(fee, STAmount(m), xrpIssue());
1403 };
1404
1405 // Add an attestation to a claim id that has already reached quorum.
1406 // This should succeed and share in the reward.
1407 // note: this is true only when either:
1408 // 1. dest account is not specified, so transfer requires a claim
1409 // 2. or the extra attestation is sent in the same batch as the
1410 // one reaching quorum
1411 for (auto withClaim : {true})
1412 {
1413 XEnv mcEnv(*this);
1414 XEnv scEnv(*this, true);
1415 std::uint32_t const claimID = 1;
1416
1417 mcEnv.tx(createBridge(mcDoor, jvb)).close();
1418
1421 .close()
1423 .close();
1424
1425 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1426
1427 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1428 auto const amt = XRP(1000);
1429 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
1430
1431 BalanceTransfer transfer(scEnv, Account::kMaster, scBob, scAlice, payees, withClaim);
1432
1433 scEnv
1435 scAttester,
1436 jvb,
1437 mcAlice,
1438 amt,
1439 payees,
1440 true,
1441 claimID,
1442 dst,
1443 signers,
1445 .close();
1446 scEnv
1448 scAttester,
1449 jvb,
1450 mcAlice,
1451 amt,
1453 true,
1454 claimID,
1455 dst,
1457 .close();
1458
1459 if (withClaim)
1460 {
1461 BEAST_EXPECT(transfer.hasNotHappened());
1462
1463 // need to submit a claim transactions
1464 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
1465 BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted
1466 BEAST_EXPECT(scEnv.claimID(jvb) == claimID);
1467 }
1468
1469 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardEveryone));
1470 }
1471
1472 // Test that signature weights are correctly handled. Assign
1473 // signature weights of 1,2,4,4 and a quorum of 7. Check that the
1474 // 4,4 signatures reach a quorum, the 1,2,4, reach a quorum, but the
1475 // 4,2, 4,1 and 1,2 do not.
1476
1477 // 1,2,4 => should succeed
1478 for (auto withClaim : {false, true})
1479 {
1480 XEnv mcEnv(*this);
1481 XEnv scEnv(*this, true);
1482
1483 std::uint32_t const quorum7 = 7;
1484 std::vector<Signer> const signers = [] {
1485 static constexpr int kNumSigners = 4;
1486 std::uint32_t const weights[] = {1, 2, 4, 4};
1487
1488 std::vector<Signer> result;
1489 result.reserve(kNumSigners);
1490 for (int i = 0; i < kNumSigners; ++i)
1491 {
1492 using namespace std::literals;
1493 auto const a = Account("signer_"s + std::to_string(i));
1494 result.emplace_back(a, weights[i]);
1495 }
1496 return result;
1497 }();
1498
1499 mcEnv.tx(createBridge(mcDoor, jvb)).close();
1500
1503 .close()
1505 .close();
1506 std::uint32_t const claimID = 1;
1507 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1508
1509 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1510 auto const amt = XRP(1000);
1511
1512 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
1513
1514 BalanceTransfer transfer(
1515 scEnv, Account::kMaster, scBob, scAlice, &payees[0], 3, withClaim);
1516
1517 scEnv
1519 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers, 3))
1520 .close();
1521
1522 if (withClaim)
1523 {
1524 BEAST_EXPECT(transfer.hasNotHappened());
1525
1526 // need to submit a claim transactions
1527 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
1528 }
1529
1530 BEAST_EXPECT(!scEnv.claimID(jvb, 1)); // claim id deleted
1531
1532 BEAST_EXPECT(transfer.hasHappened(amt, divide(reward, STAmount(3), reward.asset())));
1533 }
1534
1535 // 4,4 => should succeed
1536 for (auto withClaim : {false, true})
1537 {
1538 XEnv mcEnv(*this);
1539 XEnv scEnv(*this, true);
1540
1541 std::uint32_t const quorum7 = 7;
1542 std::vector<Signer> const signers = [] {
1543 static constexpr int kNumSigners = 4;
1544 std::uint32_t const weights[] = {1, 2, 4, 4};
1545
1546 std::vector<Signer> result;
1547 result.reserve(kNumSigners);
1548 for (int i = 0; i < kNumSigners; ++i)
1549 {
1550 using namespace std::literals;
1551 auto const a = Account("signer_"s + std::to_string(i));
1552 result.emplace_back(a, weights[i]);
1553 }
1554 return result;
1555 }();
1556 STAmount const splitReward = divide(reward, STAmount(signers.size()), reward.asset());
1557
1558 mcEnv.tx(createBridge(mcDoor, jvb)).close();
1559
1562 .close()
1564 .close();
1565 std::uint32_t const claimID = 1;
1566 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1567
1568 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1569 auto const amt = XRP(1000);
1570
1571 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
1572
1573 BalanceTransfer transfer(
1574 scEnv, Account::kMaster, scBob, scAlice, &payees[2], 2, withClaim);
1575
1576 scEnv
1578 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers, 2, 2))
1579 .close();
1580
1581 if (withClaim)
1582 {
1583 BEAST_EXPECT(transfer.hasNotHappened());
1584
1585 // need to submit a claim transactions
1586 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
1587 }
1588
1589 BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted
1590
1591 BEAST_EXPECT(transfer.hasHappened(amt, divide(reward, STAmount(2), reward.asset())));
1592 }
1593
1594 // 1,2 => should fail
1595 for (auto withClaim : {false, true})
1596 {
1597 XEnv mcEnv(*this);
1598 XEnv scEnv(*this, true);
1599
1600 std::uint32_t const quorum7 = 7;
1601 std::vector<Signer> const signers = [] {
1602 static constexpr int kNumSigners = 4;
1603 std::uint32_t const weights[] = {1, 2, 4, 4};
1604
1605 std::vector<Signer> result;
1606 result.reserve(kNumSigners);
1607 for (int i = 0; i < kNumSigners; ++i)
1608 {
1609 using namespace std::literals;
1610 auto const a = Account("signer_"s + std::to_string(i));
1611 result.emplace_back(a, weights[i]);
1612 }
1613 return result;
1614 }();
1615
1616 mcEnv.tx(createBridge(mcDoor, jvb)).close();
1617
1620 .close()
1622 .close();
1623
1624 std::uint32_t const claimID = 1;
1625 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1626
1627 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1628 auto const amt = XRP(1000);
1629 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
1630
1631 BalanceTransfer transfer(
1632 scEnv, Account::kMaster, scBob, scAlice, &payees[0], 2, withClaim);
1633
1634 scEnv
1636 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers, 2))
1637 .close();
1638 if (withClaim)
1639 {
1640 BEAST_EXPECT(transfer.hasNotHappened());
1641
1642 // need to submit a claim transactions
1643 scEnv
1644 .tx(xchainClaim(scAlice, jvb, claimID, amt, scBob),
1646 .close();
1647 }
1648
1649 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id still present
1650 BEAST_EXPECT(transfer.hasNotHappened());
1651 }
1652
1653 // 2,4 => should fail
1654 for (auto withClaim : {false, true})
1655 {
1656 XEnv mcEnv(*this);
1657 XEnv scEnv(*this, true);
1658
1659 std::uint32_t const quorum7 = 7;
1660 std::vector<Signer> const signers = [] {
1661 static constexpr int kNumSigners = 4;
1662 std::uint32_t const weights[] = {1, 2, 4, 4};
1663
1664 std::vector<Signer> result;
1665 result.reserve(kNumSigners);
1666 for (int i = 0; i < kNumSigners; ++i)
1667 {
1668 using namespace std::literals;
1669 auto const a = Account("signer_"s + std::to_string(i));
1670 result.emplace_back(a, weights[i]);
1671 }
1672 return result;
1673 }();
1674
1675 mcEnv.tx(createBridge(mcDoor, jvb)).close();
1676
1679 .close()
1681 .close();
1682
1683 std::uint32_t const claimID = 1;
1684 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
1685
1686 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
1687 auto const amt = XRP(1000);
1688
1689 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
1690
1691 BalanceTransfer transfer(
1692 scEnv, Account::kMaster, scBob, scAlice, &payees[1], 2, withClaim);
1693
1694 scEnv
1696 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers, 2, 1))
1697 .close();
1698
1699 if (withClaim)
1700 {
1701 BEAST_EXPECT(transfer.hasNotHappened());
1702
1703 // need to submit a claim transactions
1704 scEnv
1705 .tx(xchainClaim(scAlice, jvb, claimID, amt, scBob),
1707 .close();
1708 }
1709
1710 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id still present
1711 BEAST_EXPECT(transfer.hasNotHappened());
1712 }
1713
1714 // Confirm that account create transactions happen in the correct
1715 // order. If they reach quorum out of order they should not execute
1716 // until all the previous created transactions have occurred.
1717 // Re-adding an attestation should move funds.
1718 {
1719 XEnv mcEnv(*this);
1720 XEnv scEnv(*this, true);
1721 auto const amt = XRP(1000);
1722 auto const amtPlusReward = amt + reward;
1723
1724 {
1725 test::Balance const door(mcEnv, mcDoor);
1726 test::Balance const carol(mcEnv, mcCarol);
1727
1728 mcEnv.tx(createBridge(mcDoor, jvb, reward, XRP(20)))
1729 .close()
1733 .close();
1734
1735 BEAST_EXPECT(
1736 door.diff() == (multiply(amtPlusReward, STAmount(3), xrpIssue()) - fee));
1737 BEAST_EXPECT(carol.diff() == -(amt + reward + fee));
1738 }
1739
1742 .close();
1743
1744 {
1745 // send first batch of account create attest for all 3
1746 // account create
1747 test::Balance const attester(scEnv, scAttester);
1748 test::Balance const door(scEnv, Account::kMaster);
1749
1750 scEnv.multiTx(attCreateAcctVec(1, amt, scuAlice, 2))
1751 .multiTx(attCreateAcctVec(3, amt, scuCarol, 2))
1752 .multiTx(attCreateAcctVec(2, amt, scuBob, 2))
1753 .close();
1754
1755 BEAST_EXPECT(door.diff() == STAmount(0));
1756 // att_create_acct_vec return vectors of size 2, so 2*3 txns
1757 BEAST_EXPECT(attester.diff() == -multiTtxFee(6));
1758
1759 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // ca claim id present
1760 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present
1761 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present
1762 BEAST_EXPECT(scEnv.claimCount(jvb) == 0); // claim count still 0
1763 }
1764
1765 {
1766 // complete attestations for 2nd account create => should
1767 // not complete
1768 test::Balance const attester(scEnv, scAttester);
1769 test::Balance const door(scEnv, Account::kMaster);
1770
1771 scEnv.multiTx(attCreateAcctVec(2, amt, scuBob, 3, 2)).close();
1772
1773 BEAST_EXPECT(door.diff() == STAmount(0));
1774 // att_create_acct_vec return vectors of size 3, so 3 txns
1775 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1776
1777 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // ca claim id present
1778 BEAST_EXPECT(scEnv.claimCount(jvb) == 0); // claim count still 0
1779 }
1780
1781 {
1782 // complete attestations for 3rd account create => should
1783 // not complete
1784 test::Balance const attester(scEnv, scAttester);
1785 test::Balance const door(scEnv, Account::kMaster);
1786
1787 scEnv.multiTx(attCreateAcctVec(3, amt, scuCarol, 3, 2)).close();
1788
1789 BEAST_EXPECT(door.diff() == STAmount(0));
1790 // att_create_acct_vec return vectors of size 3, so 3 txns
1791 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1792
1793 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // ca claim id present
1794 BEAST_EXPECT(scEnv.claimCount(jvb) == 0); // claim count still 0
1795 }
1796
1797 {
1798 // complete attestations for 1st account create => account
1799 // should be created
1800 test::Balance const attester(scEnv, scAttester);
1801 test::Balance const door(scEnv, Account::kMaster);
1802
1803 scEnv.multiTx(attCreateAcctVec(1, amt, scuAlice, 3, 1)).close();
1804
1805 BEAST_EXPECT(door.diff() == -amtPlusReward);
1806 // att_create_acct_vec return vectors of size 3, so 3 txns
1807 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1808 BEAST_EXPECT(scEnv.balance(scuAlice) == amt);
1809
1810 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id 1 deleted
1811 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present
1812 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
1813 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count now 1
1814 }
1815
1816 {
1817 // resend attestations for 3rd account create => still
1818 // should not complete
1819 test::Balance const attester(scEnv, scAttester);
1820 test::Balance const door(scEnv, Account::kMaster);
1821
1822 scEnv.multiTx(attCreateAcctVec(3, amt, scuCarol, 3, 2)).close();
1823
1824 BEAST_EXPECT(door.diff() == STAmount(0));
1825 // att_create_acct_vec return vectors of size 3, so 3 txns
1826 BEAST_EXPECT(attester.diff() == -multiTtxFee(3));
1827
1828 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 2)); // claim id 2 present
1829 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
1830 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count still 1
1831 }
1832
1833 {
1834 // resend attestations for 2nd account create => account
1835 // should be created
1836 test::Balance const attester(scEnv, scAttester);
1837 test::Balance const door(scEnv, Account::kMaster);
1838
1839 scEnv.multiTx(attCreateAcctVec(2, amt, scuBob, 1)).close();
1840
1841 BEAST_EXPECT(door.diff() == -amtPlusReward);
1842 BEAST_EXPECT(attester.diff() == -fee);
1843 BEAST_EXPECT(scEnv.balance(scuBob) == amt);
1844
1845 BEAST_EXPECT(!scEnv.caClaimID(jvb, 2)); // claim id 2 deleted
1846 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 3)); // claim id 3 present
1847 BEAST_EXPECT(scEnv.claimCount(jvb) == 2); // claim count now 2
1848 }
1849 {
1850 // resend attestations for 3rc account create => account
1851 // should be created
1852 test::Balance const attester(scEnv, scAttester);
1853 test::Balance const door(scEnv, Account::kMaster);
1854
1855 scEnv.multiTx(attCreateAcctVec(3, amt, scuCarol, 1)).close();
1856
1857 BEAST_EXPECT(door.diff() == -amtPlusReward);
1858 BEAST_EXPECT(attester.diff() == -fee);
1859 BEAST_EXPECT(scEnv.balance(scuCarol) == amt);
1860
1861 BEAST_EXPECT(!scEnv.caClaimID(jvb, 3)); // claim id 3 deleted
1862 BEAST_EXPECT(scEnv.claimCount(jvb) == 3); // claim count now 3
1863 }
1864 }
1865
1866 // Check that creating an account with less than the minimum reserve
1867 // fails.
1868 {
1869 XEnv mcEnv(*this);
1870 XEnv scEnv(*this, true);
1871
1872 auto const amt = res0 - XRP(1);
1873 auto const amtPlusReward = amt + reward;
1874
1875 mcEnv.tx(createBridge(mcDoor, jvb, reward, XRP(20))).close();
1876
1877 {
1878 test::Balance const door(mcEnv, mcDoor);
1879 test::Balance const carol(mcEnv, mcCarol);
1880
1882
1883 BEAST_EXPECT(door.diff() == amtPlusReward);
1884 BEAST_EXPECT(carol.diff() == -(amtPlusReward + fee));
1885 }
1886
1889 .close();
1890
1891 test::Balance const attester(scEnv, scAttester);
1892 test::Balance const door(scEnv, Account::kMaster);
1893
1894 scEnv.multiTx(attCreateAcctVec(1, amt, scuAlice, 2)).close();
1895 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
1896 BEAST_EXPECT(scEnv.claimCount(jvb) == 0); // claim count is one less
1897
1898 scEnv.multiTx(attCreateAcctVec(1, amt, scuAlice, 2, 2)).close();
1899 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
1900 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count was incremented
1901
1902 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
1903 BEAST_EXPECT(door.diff() == -reward);
1904 BEAST_EXPECT(!scEnv.account(scuAlice));
1905 }
1906
1907 // Check that sending funds with an account create txn to an
1908 // existing account works.
1909 {
1910 XEnv mcEnv(*this);
1911 XEnv scEnv(*this, true);
1912
1913 auto const amt = XRP(111);
1914 auto const amtPlusReward = amt + reward;
1915
1916 mcEnv.tx(createBridge(mcDoor, jvb, reward, XRP(20))).close();
1917
1918 {
1919 test::Balance const door(mcEnv, mcDoor);
1920 test::Balance const carol(mcEnv, mcCarol);
1921
1923
1924 BEAST_EXPECT(door.diff() == amtPlusReward);
1925 BEAST_EXPECT(carol.diff() == -(amtPlusReward + fee));
1926 }
1927
1930 .close();
1931
1932 test::Balance const attester(scEnv, scAttester);
1933 test::Balance const door(scEnv, Account::kMaster);
1934 test::Balance const alice(scEnv, scAlice);
1935
1936 scEnv.multiTx(attCreateAcctVec(1, amt, scAlice, 2)).close();
1937 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
1938 BEAST_EXPECT(scEnv.claimCount(jvb) == 0); // claim count is one less
1939
1940 scEnv.multiTx(attCreateAcctVec(1, amt, scAlice, 2, 2)).close();
1941 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
1942 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count was incremented
1943
1944 BEAST_EXPECT(door.diff() == -amtPlusReward);
1945 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
1946 BEAST_EXPECT(alice.diff() == amt);
1947 }
1948
1949 // Check that sending funds to an existing account with deposit auth
1950 // set fails for account create transactions.
1951 {
1952 XEnv mcEnv(*this);
1953 XEnv scEnv(*this, true);
1954
1955 auto const amt = XRP(1000);
1956 auto const amtPlusReward = amt + reward;
1957
1958 mcEnv.tx(createBridge(mcDoor, jvb, reward, XRP(20))).close();
1959
1960 {
1961 test::Balance const door(mcEnv, mcDoor);
1962 test::Balance const carol(mcEnv, mcCarol);
1963
1965
1966 BEAST_EXPECT(door.diff() == amtPlusReward);
1967 BEAST_EXPECT(carol.diff() == -(amtPlusReward + fee));
1968 }
1969
1972 .tx(fset("scAlice", asfDepositAuth)) // set deposit auth
1973 .close();
1974
1975 test::Balance const attester(scEnv, scAttester);
1976 test::Balance const door(scEnv, Account::kMaster);
1977 test::Balance const alice(scEnv, scAlice);
1978
1979 scEnv.multiTx(attCreateAcctVec(1, amt, scAlice, 2)).close();
1980 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
1981 BEAST_EXPECT(scEnv.claimCount(jvb) == 0); // claim count is one less
1982
1983 scEnv.multiTx(attCreateAcctVec(1, amt, scAlice, 2, 2)).close();
1984 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
1985 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count was incremented
1986
1987 BEAST_EXPECT(door.diff() == -reward);
1988 BEAST_EXPECT(attester.diff() == -multiTtxFee(4));
1989 BEAST_EXPECT(alice.diff() == STAmount(0));
1990 }
1991
1992 // If an account is unable to pay the reserve, check that it fails.
1993 // [greg todo] I don't know what this should test??
1994
1995 // If an attestation already exists for that server and claim id,
1996 // the new attestation should replace the old attestation
1997 {
1998 XEnv mcEnv(*this);
1999 XEnv scEnv(*this, true);
2000 auto const amt = XRP(1000);
2001 auto const amtPlusReward = amt + reward;
2002
2003 {
2004 test::Balance const door(mcEnv, mcDoor);
2005 test::Balance const carol(mcEnv, mcCarol);
2006
2007 mcEnv.tx(createBridge(mcDoor, jvb, reward, XRP(20)))
2008 .close()
2010 .close() // make sure Alice gets claim #1
2012 .close() // make sure Bob gets claim #2
2014 .close(); // and Carol will get claim #3
2015
2016 BEAST_EXPECT(
2017 door.diff() == (multiply(amtPlusReward, STAmount(3), xrpIssue()) - fee));
2018 BEAST_EXPECT(carol.diff() == -(amt + reward + fee));
2019 }
2020
2021 std::uint32_t const redQuorum = 2;
2024 .close();
2025
2026 {
2027 test::Balance const attester(scEnv, scAttester);
2028 test::Balance const door(scEnv, Account::kMaster);
2029 auto const badAmt = XRP(10);
2030 std::uint32_t txCount = 0;
2031
2032 // send attestations with incorrect amounts to for all 3
2033 // AccountCreate. They will be replaced later
2034 scEnv.multiTx(attCreateAcctVec(1, badAmt, scuAlice, 1))
2035 .multiTx(attCreateAcctVec(2, badAmt, scuBob, 1, 2))
2036 .multiTx(attCreateAcctVec(3, badAmt, scuCarol, 1, 1))
2037 .close();
2038 txCount += 3;
2039
2040 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 1), "claim id 1 created");
2041 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 2), "claim id 2 created");
2042 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 3), "claim id 3 created");
2043
2044 // note: if we send inconsistent attestations in the same
2045 // batch, the transaction errors.
2046
2047 // from now on we send correct attestations
2048 scEnv.multiTx(attCreateAcctVec(1, amt, scuAlice, 1, 0))
2049 .multiTx(attCreateAcctVec(2, amt, scuBob, 1, 2))
2050 .multiTx(attCreateAcctVec(3, amt, scuCarol, 1, 4))
2051 .close();
2052 txCount += 3;
2053
2054 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 1), "claim id 1 still there");
2055 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 2), "claim id 2 still there");
2056 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 3), "claim id 3 still there");
2057 BEAST_EXPECTS(scEnv.claimCount(jvb) == 0, "No account created yet");
2058
2059 scEnv.multiTx(attCreateAcctVec(3, amt, scuCarol, 1, 1)).close();
2060 txCount += 1;
2061
2062 BEAST_EXPECTS(!!scEnv.caClaimID(jvb, 3), "claim id 3 still there");
2063 BEAST_EXPECTS(scEnv.claimCount(jvb) == 0, "No account created yet");
2064
2065 scEnv.multiTx(attCreateAcctVec(1, amt, scuAlice, 1, 2)).close();
2066 txCount += 1;
2067
2068 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 deleted");
2069 BEAST_EXPECTS(scEnv.claimCount(jvb) == 1, "scuAlice created");
2070
2071 scEnv.multiTx(attCreateAcctVec(2, amt, scuBob, 1, 3))
2072 .multiTx(
2073 attCreateAcctVec(1, amt, scuAlice, 1, 3),
2075 .close();
2076 txCount += 2;
2077
2078 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 2), "claim id 2 deleted");
2079 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 1), "claim id 1 not added");
2080 BEAST_EXPECTS(scEnv.claimCount(jvb) == 2, "scuAlice & scuBob created");
2081
2082 scEnv.multiTx(attCreateAcctVec(3, amt, scuCarol, 1, 0)).close();
2083 txCount += 1;
2084
2085 BEAST_EXPECTS(!scEnv.caClaimID(jvb, 3), "claim id 3 deleted");
2086 BEAST_EXPECTS(scEnv.claimCount(jvb) == 3, "All 3 accounts created");
2087
2088 // because of the division of the rewards among attesters,
2089 // sometimes a couple drops are left over unspent in the
2090 // door account (here 2 drops)
2091 BEAST_EXPECT(
2092 multiply(amtPlusReward, STAmount(3), xrpIssue()) + door.diff() < drops(3));
2093 BEAST_EXPECT(attester.diff() == -multiTtxFee(txCount));
2094 BEAST_EXPECT(scEnv.balance(scuAlice) == amt);
2095 BEAST_EXPECT(scEnv.balance(scuBob) == amt);
2096 BEAST_EXPECT(scEnv.balance(scuCarol) == amt);
2097 }
2098 }
2099
2100 // If attestation moves funds, confirm the claim ledger objects are
2101 // removed (for both account create and "regular" transactions)
2102 // [greg] we do this in all attestation tests
2103
2104 // coverage test: add_attestation transaction with incorrect flag
2105 {
2106 XEnv scEnv(*this, true);
2109 .close()
2111 scAttester, jvb, mcAlice, XRP(1000), payees[0], true, 1, {}, signers[0]),
2112 Txflags(tfFillOrKill),
2114 .close();
2115 }
2116
2117 // coverage test: add_attestation with xchain feature
2118 // disabled
2119 {
2120 XEnv scEnv(*this, true);
2123 .disableFeature(featureXChainBridge)
2124 .close()
2126 scAttester, jvb, mcAlice, XRP(1000), payees[0], true, 1, {}, signers[0]),
2128 .close();
2129 }
2130 }
2131
2132 void
2134 {
2135 using namespace jtx;
2136
2137 testcase("Add Non Batch Claim Attestation");
2138
2139 {
2140 XEnv mcEnv(*this);
2141 XEnv scEnv(*this, true);
2142 std::uint32_t const claimID = 1;
2143
2144 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2145
2148 .close()
2150 .close();
2151
2152 BEAST_EXPECT(!!scEnv.claimID(jvb, claimID)); // claim id present
2153
2154 Account const dst{scBob};
2155 auto const amt = XRP(1000);
2156 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2157
2158 auto const dstStartBalance = scEnv.env.balance(dst);
2159
2160 for (int i = 0; i < signers.size(); ++i)
2161 {
2162 auto const att = claimAttestation(
2163 scAttester, jvb, mcAlice, amt, payees[i], true, claimID, dst, signers[i]);
2164
2165 TER const expectedTER = i < quorum ? tesSUCCESS : TER{tecXCHAIN_NO_CLAIM_ID};
2166 scEnv.tx(att, Ter(expectedTER)).close();
2167
2168 if (i + 1 < quorum)
2169 {
2170 BEAST_EXPECT(dstStartBalance == scEnv.env.balance(dst));
2171 }
2172 else
2173 {
2174 BEAST_EXPECT(dstStartBalance + amt == scEnv.env.balance(dst));
2175 }
2176 }
2177 BEAST_EXPECT(dstStartBalance + amt == scEnv.env.balance(dst));
2178 }
2179
2180 {
2196
2197 XEnv mcEnv(*this);
2198 XEnv scEnv(*this, true);
2199 auto const amt = XRP(1000);
2200 std::uint32_t const claimID = 1;
2201
2202 for (auto i = 0; i < kUtXchainDefaultNumSigners - 2; ++i)
2203 scEnv.fund(amt, altSigners[i].account);
2204
2205 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2206
2209 .close()
2211 .close();
2212
2213 Account const dst{scBob};
2214 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2215 auto const dstStartBalance = scEnv.env.balance(dst);
2216
2217 {
2218 // G1: master key
2219 auto att = claimAttestation(
2220 scAttester, jvb, mcAlice, amt, payees[0], true, claimID, dst, altSigners[0]);
2221 scEnv.tx(att).close();
2222 }
2223 {
2224 // G2: regular key
2225 // alt_signers[0] is the regular key of alt_signers[1]
2226 // There should be 2 attestations after the transaction
2227 scEnv.tx(jtx::regkey(altSigners[1].account, altSigners[0].account)).close();
2228 auto att = claimAttestation(
2229 scAttester, jvb, mcAlice, amt, payees[1], true, claimID, dst, altSigners[0]);
2230 att[sfAttestationSignerAccount.getJsonName()] = altSigners[1].account.human();
2231 scEnv.tx(att).close();
2232 }
2233 {
2234 // B3: public key and non-exist (unfunded) account mismatch
2235 // G3: public key and non-exist (unfunded) account match
2236 auto const unfundedSigner1 = altSigners[kUtXchainDefaultNumSigners - 1];
2237 auto const unfundedSigner2 = altSigners[kUtXchainDefaultNumSigners - 2];
2238 auto att = claimAttestation(
2239 scAttester,
2240 jvb,
2241 mcAlice,
2242 amt,
2244 true,
2245 claimID,
2246 dst,
2247 unfundedSigner1);
2248 att[sfAttestationSignerAccount.getJsonName()] = unfundedSigner2.account.human();
2250 att[sfAttestationSignerAccount.getJsonName()] = unfundedSigner1.account.human();
2251 scEnv.tx(att).close();
2252 }
2253 {
2254 // B2: single item signer list
2255 std::vector<Signer> tempSignerList = {signers[0]};
2256 scEnv.tx(jtx::signers(altSigners[2].account, 1, tempSignerList));
2257 auto att = claimAttestation(
2258 scAttester,
2259 jvb,
2260 mcAlice,
2261 amt,
2262 payees[2],
2263 true,
2264 claimID,
2265 dst,
2266 tempSignerList.front());
2267 att[sfAttestationSignerAccount.getJsonName()] = altSigners[2].account.human();
2269 }
2270 {
2271 // B1: disabled master key
2272 scEnv.tx(fset(altSigners[2].account, asfDisableMaster, 0)).close();
2273 auto att = claimAttestation(
2274 scAttester, jvb, mcAlice, amt, payees[2], true, claimID, dst, altSigners[2]);
2276 }
2277 {
2278 // --B4: not on signer list
2279 auto att = claimAttestation(
2280 scAttester, jvb, mcAlice, amt, payees[0], true, claimID, dst, signers[0]);
2281 scEnv.tx(att, Ter(tecNO_PERMISSION)).close();
2282 }
2283 {
2284 // --B5: missing sfAttestationSignerAccount field
2285 // Then submit the one with the field. Should reach quorum.
2286 auto att = claimAttestation(
2287 scAttester, jvb, mcAlice, amt, payees[3], true, claimID, dst, altSigners[3]);
2288 att.removeMember(sfAttestationSignerAccount.getJsonName());
2289 scEnv.tx(att, Ter(temMALFORMED)).close();
2290 BEAST_EXPECT(dstStartBalance == scEnv.env.balance(dst));
2291 att[sfAttestationSignerAccount.getJsonName()] = altSigners[3].account.human();
2292 scEnv.tx(att).close();
2293 BEAST_EXPECT(dstStartBalance + amt == scEnv.env.balance(dst));
2294 }
2295 }
2296 }
2297
2298 void
2300 {
2301 using namespace jtx;
2302
2303 testcase("Add Non Batch Account Create Attestation");
2304
2305 XEnv mcEnv(*this);
2306 XEnv scEnv(*this, true);
2307
2308 XRPAmount const txFee = mcEnv.txFee();
2309
2310 Account const a{"a"};
2311 Account const doorA{"doorA"};
2312
2313 STAmount const funds{XRP(10000)};
2314 mcEnv.fund(funds, a);
2315 mcEnv.fund(funds, doorA);
2316
2317 Account const ua{"ua"}; // unfunded account we want to create
2318
2319 BridgeDef xrpB{
2320 .doorA = doorA,
2321 .issueA = xrpIssue(),
2322 .doorB = Account::kMaster,
2323 .issueB = xrpIssue(),
2324 .reward = XRP(1), // reward
2325 .minAccountCreate = XRP(20), // minAccountCreate
2326 .quorum = 4, // quorum
2327 .signers = signers,
2328 .jvb = json::ValueType::Null};
2329
2330 xrpB.initBridge(mcEnv, scEnv);
2331
2332 auto const amt = XRP(777);
2333 auto const amtPlusReward = amt + xrpB.reward;
2334 {
2335 test::Balance const balDoorA(mcEnv, doorA);
2336 test::Balance const balA(mcEnv, a);
2337
2338 mcEnv.tx(sidechainXchainAccountCreate(a, xrpB.jvb, ua, amt, xrpB.reward)).close();
2339
2340 BEAST_EXPECT(balDoorA.diff() == amtPlusReward);
2341 BEAST_EXPECT(balA.diff() == -(amtPlusReward + txFee));
2342 }
2343
2344 for (int i = 0; i < signers.size(); ++i)
2345 {
2346 auto const att = createAccountAttestation(
2347 signers[0].account,
2348 xrpB.jvb,
2349 a,
2350 amt,
2351 xrpB.reward,
2352 signers[i].account,
2353 true,
2354 1,
2355 ua,
2356 signers[i]);
2357 TER const expectedTER =
2359
2360 scEnv.tx(att, Ter(expectedTER)).close();
2361 if (i + 1 < xrpB.quorum)
2362 {
2363 BEAST_EXPECT(!scEnv.env.le(ua));
2364 }
2365 else
2366 {
2367 BEAST_EXPECT(scEnv.env.le(ua));
2368 }
2369 }
2370 BEAST_EXPECT(scEnv.env.le(ua));
2371 }
2372
2373 void
2375 {
2376 using namespace jtx;
2377
2378 XRPAmount const res0 = reserve(0);
2379 XRPAmount const fee = txFee();
2380
2381 testcase("Claim");
2382
2383 // Claim where the amount matches what is attested to, to an account
2384 // that exists, and there are enough attestations to reach a quorum
2385 // => should succeed
2386 // -----------------------------------------------------------------
2387 for (auto withClaim : {false, true})
2388 {
2389 XEnv mcEnv(*this);
2390 XEnv scEnv(*this, true);
2391
2392 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2393
2396 .close()
2398 .close();
2399
2400 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2401 auto const amt = XRP(1000);
2402 std::uint32_t const claimID = 1;
2403 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2404
2405 BalanceTransfer transfer(
2406 scEnv,
2408 scBob,
2409 scAlice,
2410 &payees[0],
2412 withClaim);
2413
2414 scEnv
2416 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers))
2417 .close();
2418 if (withClaim)
2419 {
2420 BEAST_EXPECT(transfer.hasNotHappened());
2421
2422 // need to submit a claim transactions
2423 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
2424 }
2425
2426 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum));
2427 }
2428
2429 // Claim with just one attestation signed by the Master key
2430 // => should not succeed
2431 // -----------------------------------------------------------------
2432 for (auto withClaim : {false, true})
2433 {
2434 XEnv mcEnv(*this);
2435 XEnv scEnv(*this, true);
2436
2437 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2438
2439 scEnv
2441 //.tx(jtx::signers(Account::kMaster, quorum, signers))
2442 .close()
2444 .close();
2445
2446 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2447 auto const amt = XRP(1000);
2448 std::uint32_t const claimID = 1;
2449 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2450
2451 BalanceTransfer transfer(
2452 scEnv, Account::kMaster, scBob, scAlice, &payees[0], 1, withClaim);
2453
2454 jtx::Signer const masterSigner(Account::kMaster);
2455 scEnv
2457 scAttester, jvb, mcAlice, amt, payees[0], true, claimID, dst, masterSigner),
2459 .close();
2460
2461 BEAST_EXPECT(transfer.hasNotHappened());
2462 }
2463
2464 // Claim with just one attestation signed by a regular key
2465 // associated to the master account
2466 // => should not succeed
2467 // -----------------------------------------------------------------
2468 for (auto withClaim : {false, true})
2469 {
2470 XEnv mcEnv(*this);
2471 XEnv scEnv(*this, true);
2472
2473 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2474
2475 scEnv
2477 //.tx(jtx::signers(Account::kMaster, quorum, signers))
2479 .close()
2481 .close();
2482
2483 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2484 auto const amt = XRP(1000);
2485 std::uint32_t const claimID = 1;
2486 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2487
2488 BalanceTransfer transfer(
2489 scEnv, Account::kMaster, scBob, scAlice, &payees[0], 1, withClaim);
2490
2491 jtx::Signer const masterSigner(payees[0]);
2492 scEnv
2494 scAttester, jvb, mcAlice, amt, payees[0], true, claimID, dst, masterSigner),
2496 .close();
2497
2498 BEAST_EXPECT(transfer.hasNotHappened());
2499 }
2500
2501 // Claim against non-existent bridge
2502 // ---------------------------------
2503 for (auto withClaim : {false, true})
2504 {
2505 XEnv mcEnv(*this);
2506 XEnv scEnv(*this, true);
2507
2508 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2509
2510 auto jvbUnknown = bridge(mcBob, xrpIssue(), Account::kMaster, xrpIssue());
2511
2514 .close()
2516 .close();
2517
2518 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2519 auto const amt = XRP(1000);
2520 std::uint32_t const claimID = 1;
2521 mcEnv.tx(xchainCommit(mcAlice, jvbUnknown, claimID, amt, dst), Ter(tecNO_ENTRY))
2522 .close();
2523
2524 BalanceTransfer transfer(scEnv, Account::kMaster, scBob, scAlice, payees, withClaim);
2525 scEnv
2527 scAttester,
2528 jvbUnknown,
2529 mcAlice,
2530 amt,
2531 payees[0],
2532 true,
2533 claimID,
2534 dst,
2535 signers[0]),
2537 .close();
2538
2539 if (withClaim)
2540 {
2541 BEAST_EXPECT(transfer.hasNotHappened());
2542
2543 // need to submit a claim transactions
2544 scEnv.tx(xchainClaim(scAlice, jvbUnknown, claimID, amt, scBob), Ter(tecNO_ENTRY))
2545 .close();
2546 }
2547
2548 BEAST_EXPECT(transfer.hasNotHappened());
2549 }
2550
2551 // Claim against non-existent claim id
2552 // -----------------------------------
2553 for (auto withClaim : {false, true})
2554 {
2555 XEnv mcEnv(*this);
2556 XEnv scEnv(*this, true);
2557
2558 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2559
2562 .close()
2564 .close();
2565
2566 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2567 auto const amt = XRP(1000);
2568 std::uint32_t const claimID = 1;
2569 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2570
2571 BalanceTransfer transfer(scEnv, Account::kMaster, scBob, scAlice, payees, withClaim);
2572
2573 // attest using non-existent claim id
2574 scEnv
2576 scAttester, jvb, mcAlice, amt, payees[0], true, 999, dst, signers[0]),
2578 .close();
2579 if (withClaim)
2580 {
2581 BEAST_EXPECT(transfer.hasNotHappened());
2582
2583 // claim using non-existent claim id
2585 .close();
2586 }
2587
2588 BEAST_EXPECT(transfer.hasNotHappened());
2589 }
2590
2591 // Claim against a claim id owned by another account
2592 // -------------------------------------------------
2593 for (auto withClaim : {false, true})
2594 {
2595 XEnv mcEnv(*this);
2596 XEnv scEnv(*this, true);
2597
2598 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2599
2602 .close()
2604 .close();
2605
2606 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2607 auto const amt = XRP(1000);
2608 std::uint32_t const claimID = 1;
2609 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2610
2611 BalanceTransfer transfer(
2612 scEnv,
2614 scBob,
2615 scAlice,
2616 &payees[0],
2618 withClaim);
2619
2620 scEnv
2622 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers))
2623 .close();
2624 if (withClaim)
2625 {
2626 BEAST_EXPECT(transfer.hasNotHappened());
2627
2628 // submit a claim transaction with the wrong account (scGw
2629 // instead of scAlice)
2630 scEnv.tx(xchainClaim(scGw, jvb, claimID, amt, scBob), Ter(tecXCHAIN_BAD_CLAIM_ID))
2631 .close();
2632 BEAST_EXPECT(transfer.hasNotHappened());
2633 }
2634 else
2635 {
2636 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum));
2637 }
2638 }
2639
2640 // Claim against a claim id with no attestations
2641 // ---------------------------------------------
2642 for (auto withClaim : {false, true})
2643 {
2644 XEnv mcEnv(*this);
2645 XEnv scEnv(*this, true);
2646
2647 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2648
2651 .close()
2653 .close();
2654
2655 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2656 auto const amt = XRP(1000);
2657 std::uint32_t const claimID = 1;
2658 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2659
2660 BalanceTransfer transfer(scEnv, Account::kMaster, scBob, scAlice, payees, withClaim);
2661
2662 // don't send any attestations
2663
2664 if (withClaim)
2665 {
2666 BEAST_EXPECT(transfer.hasNotHappened());
2667
2668 // need to submit a claim transactions
2669 scEnv
2670 .tx(xchainClaim(scAlice, jvb, claimID, amt, scBob),
2672 .close();
2673 }
2674
2675 BEAST_EXPECT(transfer.hasNotHappened());
2676 }
2677
2678 // Claim against a claim id with attestations, but not enough to
2679 // make a quorum
2680 // --------------------------------------------------------------------
2681 for (auto withClaim : {false, true})
2682 {
2683 XEnv mcEnv(*this);
2684 XEnv scEnv(*this, true);
2685
2686 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2687
2690 .close()
2692 .close();
2693
2694 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2695 auto const amt = XRP(1000);
2696 std::uint32_t const claimID = 1;
2697 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2698
2699 BalanceTransfer transfer(scEnv, Account::kMaster, scBob, scAlice, payees, withClaim);
2700
2701 auto tooFew = quorum - 1;
2702 scEnv
2704 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers, tooFew))
2705 .close();
2706 if (withClaim)
2707 {
2708 BEAST_EXPECT(transfer.hasNotHappened());
2709
2710 // need to submit a claim transactions
2711 scEnv
2712 .tx(xchainClaim(scAlice, jvb, claimID, amt, scBob),
2714 .close();
2715 }
2716
2717 BEAST_EXPECT(transfer.hasNotHappened());
2718 }
2719
2720 // Claim id of zero
2721 // ----------------
2722 for (auto withClaim : {false, true})
2723 {
2724 XEnv mcEnv(*this);
2725 XEnv scEnv(*this, true);
2726
2727 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2728
2731 .close()
2733 .close();
2734
2735 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2736 auto const amt = XRP(1000);
2737 std::uint32_t const claimID = 1;
2738 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2739
2740 BalanceTransfer transfer(scEnv, Account::kMaster, scBob, scAlice, payees, withClaim);
2741
2742 scEnv
2743 .multiTx(
2744 claimAttestations(scAttester, jvb, mcAlice, amt, payees, true, 0, dst, signers),
2746 .close();
2747 if (withClaim)
2748 {
2749 BEAST_EXPECT(transfer.hasNotHappened());
2750
2751 // need to submit a claim transactions
2753 .close();
2754 }
2755
2756 BEAST_EXPECT(transfer.hasNotHappened());
2757 }
2758
2759 // Claim issue that does not match the expected issue on the bridge
2760 // (either LockingChainIssue or IssuingChainIssue, depending on the
2761 // chain). The claim id should already have enough attestations to
2762 // reach a quorum for this amount (for a different issuer).
2763 // ---------------------------------------------------------------------
2764 for (auto withClaim : {true})
2765 {
2766 XEnv mcEnv(*this);
2767 XEnv scEnv(*this, true);
2768
2769 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2770
2773 .close()
2775 .close();
2776
2777 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2778 auto const amt = XRP(1000);
2779 std::uint32_t const claimID = 1;
2780 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2781
2782 BalanceTransfer transfer(
2783 scEnv,
2785 scBob,
2786 scAlice,
2787 &payees[0],
2789 withClaim);
2790
2791 scEnv
2793 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers))
2794 .close();
2795
2796 if (withClaim)
2797 {
2798 BEAST_EXPECT(transfer.hasNotHappened());
2799
2800 // need to submit a claim transactions
2801 scEnv.tx(xchainClaim(scAlice, jvb, claimID, scUSD(1000), scBob), Ter(temBAD_AMOUNT))
2802 .close();
2803 }
2804
2805 BEAST_EXPECT(transfer.hasNotHappened());
2806 }
2807
2808 // Claim to a destination that does not already exist on the chain
2809 // -----------------------------------------------------------------
2810 for (auto withClaim : {true})
2811 {
2812 XEnv mcEnv(*this);
2813 XEnv scEnv(*this, true);
2814
2815 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2816
2819 .close()
2821 .close();
2822
2823 auto dst(withClaim ? std::nullopt : std::optional<Account>{scuBob});
2824 auto const amt = XRP(1000);
2825 std::uint32_t const claimID = 1;
2826 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2827
2828 BalanceTransfer transfer(
2829 scEnv,
2831 scBob,
2832 scAlice,
2833 &payees[0],
2835 withClaim);
2836
2837 scEnv
2839 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers))
2840 .close();
2841 if (withClaim)
2842 {
2843 BEAST_EXPECT(transfer.hasNotHappened());
2844
2845 // need to submit a claim transactions
2846 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scuBob), Ter(tecNO_DST)).close();
2847 }
2848
2849 BEAST_EXPECT(transfer.hasNotHappened());
2850 }
2851
2852 // Claim where the claim id owner does not have enough XRP to pay
2853 // the reward
2854 // ------------------------------------------------------------------
2855 for (auto withClaim : {false, true})
2856 {
2857 XEnv mcEnv(*this);
2858 XEnv scEnv(*this, true);
2859
2860 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2861 STAmount const hugeReward{XRP(20000)};
2862 BEAST_EXPECT(hugeReward > scEnv.balance(scAlice));
2863
2864 scEnv.tx(createBridge(Account::kMaster, jvb, hugeReward))
2866 .close()
2867 .tx(xchainCreateClaimId(scAlice, jvb, hugeReward, mcAlice))
2868 .close();
2869
2870 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2871 auto const amt = XRP(1000);
2872 std::uint32_t const claimID = 1;
2873 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2874
2875 BalanceTransfer transfer(
2876 scEnv,
2878 scBob,
2879 scAlice,
2880 &payees[0],
2882 withClaim);
2883
2884 if (withClaim)
2885 {
2886 scEnv
2888 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers))
2889 .close();
2890 BEAST_EXPECT(transfer.hasNotHappened());
2891
2892 // need to submit a claim transactions
2893 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob), Ter(tecUNFUNDED_PAYMENT))
2894 .close();
2895 }
2896 else
2897 {
2898 auto txns = claimAttestations(
2899 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers);
2900 for (int i = 0; i < kUtXchainDefaultQuorum - 1; ++i)
2901 {
2902 scEnv.tx(txns[i]).close();
2903 }
2904 scEnv.tx(txns.back());
2905 scEnv.close();
2906 // The attestation should succeed, because it adds an
2907 // attestation, but the claim should fail with insufficient
2908 // funds
2909 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob), Ter(tecUNFUNDED_PAYMENT))
2910 .close();
2911 }
2912
2913 BEAST_EXPECT(transfer.hasNotHappened());
2914 }
2915
2916 // Claim where the claim id owner has enough XRP to pay the reward,
2917 // but it would put his balance below the reserve
2918 // --------------------------------------------------------------------
2919 for (auto withClaim : {false, true})
2920 {
2921 XEnv mcEnv(*this);
2922 XEnv scEnv(*this, true);
2923
2924 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2925
2928 .fund(
2929 res0 + reward,
2930 scuAlice) // just not enough because of fees
2931 .close()
2934 .close();
2935
2936 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2937 auto const amt = XRP(1000);
2938 std::uint32_t const claimID = 1;
2939 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2940
2941 BalanceTransfer transfer(scEnv, Account::kMaster, scBob, scuAlice, payees, withClaim);
2942
2943 scEnv
2945 scAttester, jvb, mcAlice, amt, payees[0], true, claimID, dst, signers[0]),
2947 .close();
2948 if (withClaim)
2949 {
2950 BEAST_EXPECT(transfer.hasNotHappened());
2951
2952 // need to submit a claim transactions
2953 scEnv
2955 .close();
2956 }
2957
2958 BEAST_EXPECT(transfer.hasNotHappened());
2959 }
2960
2961 // Pay to an account with deposit auth set
2962 // ---------------------------------------
2963 for (auto withClaim : {false, true})
2964 {
2965 XEnv mcEnv(*this);
2966 XEnv scEnv(*this, true);
2967
2968 mcEnv.tx(createBridge(mcDoor, jvb)).close();
2969
2972 .tx(fset("scBob", asfDepositAuth)) // set deposit auth
2973 .close()
2975 .close();
2976
2977 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
2978 auto const amt = XRP(1000);
2979 std::uint32_t const claimID = 1;
2980 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
2981
2982 BalanceTransfer transfer(
2983 scEnv,
2985 scBob,
2986 scAlice,
2987 &payees[0],
2989 withClaim);
2990 auto txns = claimAttestations(
2991 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers);
2992 for (int i = 0; i < kUtXchainDefaultQuorum - 1; ++i)
2993 {
2994 scEnv.tx(txns[i]).close();
2995 }
2996 if (withClaim)
2997 {
2998 scEnv.tx(txns.back()).close();
2999
3000 BEAST_EXPECT(transfer.hasNotHappened());
3001
3002 // need to submit a claim transactions
3003 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob), Ter(tecNO_PERMISSION))
3004 .close();
3005
3006 // the transfer failed, but check that we can still use the
3007 // claimID with a different account
3008 test::Balance const scCarolBal(scEnv, scCarol);
3009
3010 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scCarol)).close();
3011 BEAST_EXPECT(scCarolBal.diff() == amt);
3012 }
3013 else
3014 {
3015 scEnv.tx(txns.back()).close();
3016 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob), Ter(tecNO_PERMISSION))
3017 .close();
3018 // A way would be to remove deposit auth and resubmit the
3019 // attestations (even though the witness servers won't do
3020 // it)
3021 scEnv
3022 .tx(fset("scBob", 0, asfDepositAuth)) // clear deposit auth
3023 .close();
3024
3025 test::Balance const scBobBal(scEnv, scBob);
3026 scEnv.tx(txns.back()).close();
3027 BEAST_EXPECT(scBobBal.diff() == amt);
3028 }
3029 }
3030
3031 // Pay to an account with Destination Tag set
3032 // ------------------------------------------
3033 for (auto withClaim : {false, true})
3034 {
3035 XEnv mcEnv(*this);
3036 XEnv scEnv(*this, true);
3037
3038 mcEnv.tx(createBridge(mcDoor, jvb)).close();
3039
3042 .tx(fset("scBob", asfRequireDest)) // set dest tag
3043 .close()
3045 .close();
3046
3047 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3048 auto const amt = XRP(1000);
3049 std::uint32_t const claimID = 1;
3050 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
3051
3052 BalanceTransfer transfer(
3053 scEnv,
3055 scBob,
3056 scAlice,
3057 &payees[0],
3059 withClaim);
3060 auto txns = claimAttestations(
3061 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers);
3062 for (int i = 0; i < kUtXchainDefaultQuorum - 1; ++i)
3063 {
3064 scEnv.tx(txns[i]).close();
3065 }
3066 if (withClaim)
3067 {
3068 scEnv.tx(txns.back()).close();
3069 BEAST_EXPECT(transfer.hasNotHappened());
3070
3071 // need to submit a claim transactions
3072 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob), Ter(tecDST_TAG_NEEDED))
3073 .close();
3074
3075 // the transfer failed, but check that we can still use the
3076 // claimID with a different account
3077 test::Balance const scCarolBal(scEnv, scCarol);
3078
3079 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scCarol)).close();
3080 BEAST_EXPECT(scCarolBal.diff() == amt);
3081 }
3082 else
3083 {
3084 scEnv.tx(txns.back()).close();
3085 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob), Ter(tecDST_TAG_NEEDED))
3086 .close();
3087 // A way would be to remove the destination tag requirement
3088 // and resubmit the attestations (even though the witness
3089 // servers won't do it)
3090 scEnv
3091 .tx(fset("scBob", 0, asfRequireDest)) // clear dest tag
3092 .close();
3093
3094 test::Balance const scBobBal(scEnv, scBob);
3095
3096 scEnv.tx(txns.back()).close();
3097 BEAST_EXPECT(scBobBal.diff() == amt);
3098 }
3099 }
3100
3101 // Pay to an account with deposit auth set. Check that the attestations
3102 // are still validated and that we can used the claimID to transfer the
3103 // funds to a different account (which doesn't have deposit auth set)
3104 // --------------------------------------------------------------------
3105 {
3106 XEnv mcEnv(*this);
3107 XEnv scEnv(*this, true);
3108
3109 mcEnv.tx(createBridge(mcDoor, jvb)).close();
3110
3113 .tx(fset("scBob", asfDepositAuth)) // set deposit auth
3114 .close()
3116 .close();
3117
3118 auto dst(std::optional<Account>{scBob});
3119 auto const amt = XRP(1000);
3120 std::uint32_t const claimID = 1;
3121 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
3122
3123 // we should be able to submit the attestations, but the transfer
3124 // should not occur because dest account has deposit auth set
3125 test::Balance const scBobBal(scEnv, scBob);
3126
3128 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers));
3129 BEAST_EXPECT(scBobBal.diff() == STAmount(0));
3130
3131 // Check that check that we still can use the claimID to transfer
3132 // the amount to a different account
3133 test::Balance const scCarolBal(scEnv, scCarol);
3134
3135 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scCarol)).close();
3136 BEAST_EXPECT(scCarolBal.diff() == amt);
3137 }
3138
3139 // Claim where the amount different from what is attested to
3140 // ---------------------------------------------------------
3141 for (auto withClaim : {true})
3142 {
3143 XEnv mcEnv(*this);
3144 XEnv scEnv(*this, true);
3145
3146 mcEnv.tx(createBridge(mcDoor, jvb)).close();
3147
3150 .close()
3152 .close();
3153
3154 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3155 auto const amt = XRP(1000);
3156 std::uint32_t const claimID = 1;
3157 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
3158
3159 BalanceTransfer transfer(
3160 scEnv,
3162 scBob,
3163 scAlice,
3164 &payees[0],
3166 withClaim);
3168 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers));
3169 if (withClaim)
3170 {
3171 BEAST_EXPECT(transfer.hasNotHappened());
3172
3173 // claim wrong amount
3174 scEnv
3175 .tx(xchainClaim(scAlice, jvb, claimID, oneXrp, scBob),
3177 .close();
3178 }
3179
3180 BEAST_EXPECT(transfer.hasNotHappened());
3181 }
3182
3183 // Verify that rewards are paid from the account that owns the claim
3184 // id
3185 // --------------------------------------------------------------------
3186 for (auto withClaim : {false, true})
3187 {
3188 XEnv mcEnv(*this);
3189 XEnv scEnv(*this, true);
3190
3191 mcEnv.tx(createBridge(mcDoor, jvb)).close();
3192
3195 .close()
3197 .close();
3198
3199 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3200 auto const amt = XRP(1000);
3201 std::uint32_t const claimID = 1;
3202 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
3203
3204 BalanceTransfer transfer(
3205 scEnv,
3207 scBob,
3208 scAlice,
3209 &payees[0],
3211 withClaim);
3212 test::Balance const scAliceBal(scEnv, scAlice);
3214 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers));
3215
3216 STAmount claimCost = reward;
3217
3218 if (withClaim)
3219 {
3220 BEAST_EXPECT(transfer.hasNotHappened());
3221
3222 // need to submit a claim transactions
3223 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
3224 claimCost += fee;
3225 }
3226
3227 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum));
3228 BEAST_EXPECT(scAliceBal.diff() == -claimCost); // because reward % 4 == 0
3229 }
3230
3231 // Verify that if a reward is not evenly divisible among the reward
3232 // accounts, the remaining amount goes to the claim id owner.
3233 // ----------------------------------------------------------------
3234 for (auto withClaim : {false, true})
3235 {
3236 XEnv mcEnv(*this);
3237 XEnv scEnv(*this, true);
3238
3240
3243 .close()
3245 .close();
3246
3247 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3248 auto const amt = XRP(1000);
3249 std::uint32_t const claimID = 1;
3250 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
3251
3252 BalanceTransfer transfer(
3253 scEnv,
3255 scBob,
3256 scAlice,
3257 &payees[0],
3259 withClaim);
3260 test::Balance const scAliceBal(scEnv, scAlice);
3262 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers));
3263 STAmount claimCost = tinyReward;
3264
3265 if (withClaim)
3266 {
3267 BEAST_EXPECT(transfer.hasNotHappened());
3268
3269 // need to submit a claim transactions
3270 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
3271 claimCost += fee;
3272 }
3273
3274 BEAST_EXPECT(transfer.hasHappened(amt, tinyRewardSplit));
3275 BEAST_EXPECT(scAliceBal.diff() == -(claimCost - tinyRewardRemainder));
3276 }
3277
3278 // If a reward distribution fails for one of the reward accounts
3279 // (the reward account doesn't exist or has deposit auth set), then
3280 // the txn should still succeed, but that portion should go to the
3281 // claim id owner.
3282 // -------------------------------------------------------------------
3283 for (auto withClaim : {false, true})
3284 {
3285 XEnv mcEnv(*this);
3286 XEnv scEnv(*this, true);
3287
3288 mcEnv.tx(createBridge(mcDoor, jvb)).close();
3289
3290 std::vector<Account> altPayees{payees.begin(), payees.end() - 1};
3291 altPayees.back() = Account("inexistent");
3292
3295 .close()
3297 .close();
3298
3299 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3300 auto const amt = XRP(1000);
3301 std::uint32_t const claimID = 1;
3302 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
3303
3304 BalanceTransfer transfer(
3305 scEnv,
3307 scBob,
3308 scAlice,
3309 &payees[0],
3311 withClaim);
3313 scAttester, jvb, mcAlice, amt, altPayees, true, claimID, dst, signers));
3314
3315 if (withClaim)
3316 {
3317 BEAST_EXPECT(transfer.hasNotHappened());
3318
3319 // need to submit a claim transactions
3320 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
3321 }
3322
3323 // this also checks that only 3 * split_reward was deducted from
3324 // scAlice (the payer account), since we passed alt_payees to
3325 // BalanceTransfer
3326 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum));
3327 }
3328
3329 for (auto withClaim : {false, true})
3330 {
3331 XEnv mcEnv(*this);
3332 XEnv scEnv(*this, true);
3333
3334 mcEnv.tx(createBridge(mcDoor, jvb)).close();
3335 auto& unpaid = payees[kUtXchainDefaultQuorum - 1];
3338 .tx(fset(unpaid, asfDepositAuth))
3339 .close()
3341 .close();
3342
3343 auto dst(withClaim ? std::nullopt : std::optional<Account>{scBob});
3344 auto const amt = XRP(1000);
3345 std::uint32_t const claimID = 1;
3346 mcEnv.tx(xchainCommit(mcAlice, jvb, claimID, amt, dst)).close();
3347
3348 // balance of last signer should not change (has deposit auth)
3349 test::Balance const lastSigner(scEnv, unpaid);
3350
3351 // make sure all signers except the last one get the
3352 // split_reward
3353
3354 BalanceTransfer transfer(
3355 scEnv,
3357 scBob,
3358 scAlice,
3359 &payees[0],
3361 withClaim);
3363 scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers));
3364
3365 if (withClaim)
3366 {
3367 BEAST_EXPECT(transfer.hasNotHappened());
3368
3369 // need to submit a claim transactions
3370 scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close();
3371 }
3372
3373 // this also checks that only 3 * split_reward was deducted from
3374 // scAlice (the payer account), since we passed payees.size() -
3375 // 1 to BalanceTransfer
3376 BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum));
3377
3378 // and make sure the account with deposit auth received nothing
3379 BEAST_EXPECT(lastSigner.diff() == STAmount(0));
3380 }
3381
3382 // coverage test: xchain_claim transaction with incorrect flag
3383 XEnv(*this, true)
3385 .close()
3386 .tx(xchainClaim(scAlice, jvb, 1, XRP(1000), scBob),
3387 Txflags(tfFillOrKill),
3389 .close();
3390
3391 // coverage test: xchain_claim transaction with xchain feature
3392 // disabled
3393 XEnv(*this, true)
3395 .disableFeature(featureXChainBridge)
3396 .close()
3397 .tx(xchainClaim(scAlice, jvb, 1, XRP(1000), scBob), Ter(temDISABLED))
3398 .close();
3399
3400 // coverage test: XChainClaim::preclaim - isLockingChain = true;
3401 XEnv(*this)
3403 .close()
3405 }
3406
3407 void
3409 {
3410 using namespace jtx;
3411
3412 testcase("Bridge Create Account");
3413 XRPAmount const fee = txFee();
3414
3415 // coverage test: transferHelper() - dst == src
3416 {
3417 XEnv scEnv(*this, true);
3418
3419 auto const amt = XRP(111);
3420 auto const amtPlusReward = amt + reward;
3421
3424 .close();
3425
3426 test::Balance const door(scEnv, Account::kMaster);
3427
3428 // scEnv.tx(att_create_acct_batch1(1, amt,
3429 // Account::kMaster)).close();
3430 scEnv.multiTx(attCreateAcctVec(1, amt, Account::kMaster, 2)).close();
3431 BEAST_EXPECT(!!scEnv.caClaimID(jvb, 1)); // claim id present
3432 BEAST_EXPECT(scEnv.claimCount(jvb) == 0); // claim count is one less
3433
3434 // scEnv.tx(att_create_acct_batch2(1, amt,
3435 // Account::kMaster)).close();
3436 scEnv.multiTx(attCreateAcctVec(1, amt, Account::kMaster, 2, 2)).close();
3437 BEAST_EXPECT(!scEnv.caClaimID(jvb, 1)); // claim id deleted
3438 BEAST_EXPECT(scEnv.claimCount(jvb) == 1); // claim count was incremented
3439
3440 BEAST_EXPECT(door.diff() == -reward);
3441 }
3442
3443 // Check that creating an account with less than the minimum create
3444 // amount fails.
3445 {
3446 XEnv mcEnv(*this);
3447
3448 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3449
3450 test::Balance const door(mcEnv, mcDoor);
3451 test::Balance const carol(mcEnv, mcCarol);
3452
3453 mcEnv
3456 .close();
3457
3458 BEAST_EXPECT(door.diff() == STAmount(0));
3459 BEAST_EXPECT(carol.diff() == -fee);
3460 }
3461
3462 // Check that creating an account with invalid flags fails.
3463 {
3464 XEnv mcEnv(*this);
3465
3466 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3467
3468 test::Balance const door(mcEnv, mcDoor);
3469
3470 mcEnv
3472 Txflags(tfFillOrKill),
3474 .close();
3475
3476 BEAST_EXPECT(door.diff() == STAmount(0));
3477 }
3478
3479 // Check that creating an account with the XChainBridge feature
3480 // disabled fails.
3481 {
3482 XEnv mcEnv(*this);
3483
3484 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3485
3486 test::Balance const door(mcEnv, mcDoor);
3487
3488 mcEnv.disableFeature(featureXChainBridge)
3491 .close();
3492
3493 BEAST_EXPECT(door.diff() == STAmount(0));
3494 }
3495
3496 // Check that creating an account with a negative amount fails
3497 {
3498 XEnv mcEnv(*this);
3499
3500 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3501
3502 test::Balance const door(mcEnv, mcDoor);
3503
3504 mcEnv
3507 .close();
3508
3509 BEAST_EXPECT(door.diff() == STAmount(0));
3510 }
3511
3512 // Check that creating an account with a negative reward fails
3513 {
3514 XEnv mcEnv(*this);
3515
3516 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3517
3518 test::Balance const door(mcEnv, mcDoor);
3519
3520 mcEnv
3523 .close();
3524
3525 BEAST_EXPECT(door.diff() == STAmount(0));
3526 }
3527
3528 // Check that door account can't lock funds onto itself
3529 {
3530 XEnv mcEnv(*this);
3531
3532 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3533
3534 test::Balance const door(mcEnv, mcDoor);
3535
3536 mcEnv
3539 .close();
3540
3541 BEAST_EXPECT(door.diff() == -fee);
3542 }
3543
3544 // Check that reward matches the amount specified in bridge
3545 {
3546 XEnv mcEnv(*this);
3547
3548 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(20))).close();
3549
3550 test::Balance const door(mcEnv, mcDoor);
3551
3552 mcEnv
3555 .close();
3556
3557 BEAST_EXPECT(door.diff() == STAmount(0));
3558 }
3559 }
3560
3561 void
3563 {
3564 using namespace jtx;
3565 XRPAmount const res0 = reserve(0);
3566 XRPAmount const fee = txFee();
3567
3568 testcase("Fee dips into reserve");
3569
3570 // commit where the fee dips into the reserve, this should succeed
3571 XEnv(*this)
3573 .fund(res0 + oneXrp + fee - drops(1), mcuAlice)
3574 .close()
3576
3577 // commit where the commit amount drips into the reserve, this should
3578 // fail
3579 XEnv(*this)
3581 .fund(res0 + oneXrp - drops(1), mcuAlice)
3582 .close()
3584
3585 auto const minAccountCreate = XRP(20);
3586
3587 // account create commit where the fee dips into the reserve,
3588 // this should succeed
3589 XEnv(*this)
3590 .tx(createBridge(mcDoor, jvb, reward, minAccountCreate))
3591 .fund(res0 + fee + minAccountCreate + reward - drops(1), mcuAlice)
3592 .close()
3594 Ter(tesSUCCESS));
3595
3596 // account create commit where the commit dips into the reserve,
3597 // this should fail
3598 XEnv(*this)
3599 .tx(createBridge(mcDoor, jvb, reward, minAccountCreate))
3600 .fund(res0 + minAccountCreate + reward - drops(1), mcuAlice)
3601 .close()
3604 }
3605
3606 void
3608 {
3609 using namespace jtx;
3610
3611 testcase("Bridge Delete Door Account");
3612
3613 auto const acctDelFee{drops(XEnv(*this).env.current()->fees().increment)};
3614
3615 // Deleting an account that owns bridge should fail
3616 {
3617 XEnv mcEnv(*this);
3618
3619 mcEnv.tx(createBridge(mcDoor, jvb, XRP(1), XRP(1))).close();
3620
3621 // We don't allow an account to be deleted if its sequence
3622 // number is within 256 of the current ledger.
3623 for (size_t i = 0; i < 256; ++i)
3624 mcEnv.close();
3625
3626 // try to delete mcDoor, send funds to mcAlice
3627 mcEnv.tx(acctdelete(mcDoor, mcAlice), Fee(acctDelFee), Ter(tecHAS_OBLIGATIONS));
3628 }
3629
3630 // Deleting an account that owns a claim id should fail
3631 {
3632 XEnv scEnv(*this, true);
3633
3635 .close()
3637 .close();
3638
3639 // We don't allow an account to be deleted if its sequence
3640 // number is within 256 of the current ledger.
3641 for (size_t i = 0; i < 256; ++i)
3642 scEnv.close();
3643
3644 // try to delete scAlice, send funds to scBob
3645 scEnv.tx(acctdelete(scAlice, scBob), Fee(acctDelFee), Ter(tecHAS_OBLIGATIONS));
3646 }
3647 }
3648
3649 void
3651 {
3652 using namespace jtx;
3653
3654 testcase("Bad attestations");
3655 {
3656 // Create a bridge and add an attestation with a bad public key
3657 XEnv scEnv(*this, true);
3658 std::uint32_t const claimID = 1;
3659 std::optional<Account> const dst{scBob};
3660 auto const amt = XRP(1000);
3663 .close();
3665 auto jvAtt = claimAttestation(
3666 scAttester,
3667 jvb,
3668 mcAlice,
3669 amt,
3671 true,
3672 claimID,
3673 dst,
3675 {
3676 // Change to an invalid keytype
3677 auto k = jvAtt["PublicKey"].asString();
3678 k.at(1) = '9';
3679 jvAtt["PublicKey"] = k;
3680 }
3681 scEnv.tx(jvAtt, Ter(temMALFORMED)).close();
3682 }
3683 {
3684 // Create a bridge and add an create account attestation with a bad
3685 // public key
3686 XEnv scEnv(*this, true);
3687 std::uint32_t const createCount = 1;
3688 Account const dst{scBob};
3689 auto const amt = XRP(1000);
3690 auto const rewardAmt = XRP(1);
3693 .close();
3694 auto jvAtt = createAccountAttestation(
3695 scAttester,
3696 jvb,
3697 mcAlice,
3698 amt,
3699 rewardAmt,
3701 true,
3702 createCount,
3703 dst,
3705 {
3706 // Change to an invalid keytype
3707 auto k = jvAtt["PublicKey"].asString();
3708 k.at(1) = '9';
3709 jvAtt["PublicKey"] = k;
3710 }
3711 scEnv.tx(jvAtt, Ter(temMALFORMED)).close();
3712 }
3713 }
3714
3715 void
3734};
3735
3736// -----------------------------------------------------------
3737// -----------------------------------------------------------
3739{
3740private:
3741 static constexpr size_t kNumSigners = 5;
3742
3743 // --------------------------------------------------
3744 enum class WithClaim { No, Yes };
3756
3767
3769 using BridgeID = BridgeDef const*;
3770
3771 // tracking chain state
3772 // --------------------
3774 {
3777
3778 void
3779 init(ENV& env, jtx::Account const& acct)
3780 {
3781 startAmount = env.balance(acct);
3783 }
3784
3785 bool
3786 verify(ENV& env, jtx::Account const& acct) const
3787 {
3788 STAmount const diff{env.balance(acct) - startAmount};
3789 bool const check = diff == expectedDiff;
3790 return check;
3791 }
3792 };
3793
3794 // --------------------------------------------------
3796 {
3800
3801 ChainStateTrack(ENV& env) : env(env), txFee(env.env.current()->fees().base)
3802 {
3803 }
3804
3805 void
3806 sendAttestations(size_t signerIdx, BridgeID bridge, ClaimVec& claims)
3807 {
3808 for (auto const& c : claims)
3809 {
3810 env.tx(c).close();
3811 spendFee(bridge->signers[signerIdx].account);
3812 }
3813 claims.clear();
3814 }
3815
3816 uint32_t
3818 {
3819 size_t numSuccessful = 0;
3820 for (auto const& c : claims)
3821 {
3822 env.tx(c).close();
3823 if (env.ter() == tesSUCCESS)
3824 {
3825 counters[bridge].signers.push_back(signerIdx);
3826 numSuccessful++;
3827 }
3828 spendFee(bridge->signers[signerIdx].account);
3829 }
3830 claims.clear();
3831 return numSuccessful;
3832 }
3833
3834 void
3836 {
3837 bool callbackCalled = false;
3838
3839 // we have this "do {} while" loop because we want to process
3840 // all the account create which can reach quorum at this time
3841 // stamp.
3842 do
3843 {
3844 callbackCalled = false;
3845 // cspell: ignore attns
3846 for (size_t i = 0; i < signersAttns.size(); ++i)
3847 {
3848 for (auto& [bridge, claims] : signersAttns[i])
3849 {
3850 sendAttestations(i, bridge, claims.xferClaims);
3851
3852 auto& c = counters[bridge];
3853 auto& createClaims = claims.createClaims[c.claimCount];
3854 auto numAttns = createClaims.size();
3855 if (numAttns != 0u)
3856 {
3857 c.numCreateAttnSent += sendCreateAttestations(i, bridge, createClaims);
3858 }
3859 assert(claims.createClaims[c.claimCount].empty());
3860 }
3861 }
3862 for (auto& [bridge, c] : counters)
3863 {
3864 if (c.numCreateAttnSent >= bridge->quorum)
3865 {
3866 callbackCalled = true;
3867 c.createCallbacks[c.claimCount](c.signers);
3868 ++c.claimCount;
3869 c.numCreateAttnSent = 0;
3870 c.signers.clear();
3871 }
3872 }
3873 } while (callbackCalled);
3874 }
3875
3876 void
3877 init(jtx::Account const& acct)
3878 {
3879 accounts[acct].init(env, acct);
3880 }
3881
3882 void
3883 receive(jtx::Account const& acct, STAmount amt, std::uint64_t divisor = 1)
3884 {
3885 if (amt.asset() != xrpIssue())
3886 return;
3887 auto it = accounts.find(acct);
3888 if (it == accounts.end())
3889 {
3890 accounts[acct].init(env, acct);
3891 // we just looked up the account, so expectedDiff == 0
3892 }
3893 else
3894 {
3895 it->second.expectedDiff +=
3896 (divisor == 1 ? amt : divide(amt, STAmount(amt.asset(), divisor), amt.asset()));
3897 }
3898 }
3899
3900 void
3901 spend(jtx::Account const& acct, STAmount amt, std::uint64_t times = 1)
3902 {
3903 if (amt.asset() != xrpIssue())
3904 return;
3905 receive(
3906 acct,
3907 times == 1 ? -amt : -multiply(amt, STAmount(amt.asset(), times), amt.asset()));
3908 }
3909
3910 void
3911 transfer(jtx::Account const& from, jtx::Account const& to, STAmount amt)
3912 {
3913 spend(from, amt);
3914 receive(to, amt);
3915 }
3916
3917 void
3918 spendFee(jtx::Account const& acct, size_t times = 1)
3919 {
3920 spend(acct, txFee, times);
3921 }
3922
3923 [[nodiscard]] bool
3924 verify() const
3925 {
3926 for (auto const& [acct, state] : accounts)
3927 {
3928 if (!state.verify(env, acct))
3929 return false;
3930 }
3931 return true;
3932 }
3933
3935 {
3937
3939 uint32_t createCount{0}; // for account create. First should be 1
3940 uint32_t claimCount{0}; // for account create. Increments after quorum for
3941 // current createCount (starts at 1) is reached.
3942
3943 uint32_t numCreateAttnSent{0}; // for current claimCount
3946 };
3947
3953
3956
3962 };
3963
3965 {
3966 ChainStateTracker(ENV& aEnv, ENV& bEnv) : a(aEnv), b(bEnv)
3967 {
3968 }
3969
3970 [[nodiscard]] bool
3971 verify() const
3972 {
3973 return a.verify() && b.verify();
3974 }
3975
3976 void
3978 {
3979 a.sendAttestations();
3980 b.sendAttestations();
3981 }
3982
3983 void
3984 init(jtx::Account const& acct)
3985 {
3986 a.init(acct);
3987 b.init(acct);
3988 }
3989
3992 };
3993
4002
4003 enum class ActFlags { A2b = 1 << 0 };
4004
4005 // --------------------------------------------------
4006 template <class T>
4008 {
4010 : bridge_(bridge), st_(chainstate)
4011 {
4012 }
4013
4014 public:
4017 {
4018 return static_cast<T&>(*this).a2b() ? st_->a : st_->b;
4019 }
4020
4023 {
4024 return static_cast<T&>(*this).a2b() ? st_->b : st_->a;
4025 }
4026
4027 jtx::Account const&
4029 {
4030 return static_cast<T&>(*this).a2b() ? bridge_.doorA : bridge_.doorB;
4031 }
4032
4033 jtx::Account const&
4035 {
4036 return static_cast<T&>(*this).a2b() ? bridge_.doorB : bridge_.doorA;
4037 }
4038
4039 protected:
4042
4043 friend T;
4044 };
4045
4046 // --------------------------------------------------
4047 class SmCreateAccount : public SmBase<SmCreateAccount>
4048 {
4049 public:
4051
4053 std::shared_ptr<ChainStateTracker> const& chainstate,
4054 BridgeDef const& bridge,
4056 : Base(chainstate, bridge), cr_(std::move(create))
4057 {
4058 }
4059
4060 [[nodiscard]] bool
4061 a2b() const
4062 {
4063 return cr_.a2b;
4064 }
4065
4066 uint32_t
4068 {
4069 ChainStateTrack& st = srcState();
4070 jtx::Account const& srcdoor = srcDoor();
4071
4072 st.env
4074 cr_.from, bridge_.jvb, cr_.to, cr_.amt, cr_.reward))
4075 .close(); // needed for claim_id sequence to be correct'
4076 st.spendFee(cr_.from);
4077 st.transfer(cr_.from, srcdoor, cr_.amt);
4078 st.transfer(cr_.from, srcdoor, cr_.reward);
4079
4080 return ++st.counters[&bridge_].createCount;
4081 }
4082
4083 void
4085 {
4086 ChainStateTrack& st = destState();
4087
4088 // check all signers, but start at a random one
4089 size_t i = 0;
4090 for (i = 0; i < kNumSigners; ++i)
4091 {
4092 size_t const signerIdx = (rnd + i) % kNumSigners;
4093
4094 if (!(cr_.attested[signerIdx]))
4095 {
4096 // enqueue one attestation for this signer
4097 cr_.attested[signerIdx] = true;
4098
4099 st.signersAttns[signerIdx][&bridge_].createClaims[cr_.claimId - 1].emplace_back(
4101 bridge_.signers[signerIdx].account,
4102 bridge_.jvb,
4103 cr_.from,
4104 cr_.amt,
4105 cr_.reward,
4106 bridge_.signers[signerIdx].account,
4107 cr_.a2b,
4108 cr_.claimId,
4109 cr_.to,
4110 bridge_.signers[signerIdx]));
4111 break;
4112 }
4113 }
4114
4115 if (i == kNumSigners)
4116 return; // did not attest
4117
4118 auto& counters = st.counters[&bridge_];
4119 if (counters.createCallbacks.size() < cr_.claimId)
4120 counters.createCallbacks.resize(cr_.claimId);
4121
4122 auto completeCb = [&](std::vector<size_t> const& signers) {
4123 auto numAttestors = signers.size();
4124 st.env.close();
4125 assert(numAttestors <= std::count(cr_.attested.begin(), cr_.attested.end(), true));
4126 assert(numAttestors >= bridge_.quorum);
4127 assert(cr_.claimId - 1 == counters.claimCount);
4128
4129 auto r = cr_.reward;
4130 auto reward = divide(r, STAmount(numAttestors), r.asset());
4131
4132 for (auto i : signers)
4133 st.receive(bridge_.signers[i].account, reward);
4134
4135 st.spend(dstDoor(), reward, numAttestors);
4136 st.transfer(dstDoor(), cr_.to, cr_.amt);
4137 st.env.env.memoize(cr_.to);
4139 };
4140
4141 counters.createCallbacks[cr_.claimId - 1] = std::move(completeCb);
4142 }
4143
4144 SmState
4146 {
4147 switch (smState_)
4148 {
4149 case SmState::Initial:
4150 cr_.claimId = issueAccountCreate();
4152 break;
4153
4154 case SmState::Attesting:
4155 attest(time, rnd);
4156 break;
4157
4158 default:
4159 assert(0);
4160 break;
4161
4162 case SmState::Completed:
4163 break; // will get this once
4164 }
4165 return smState_;
4166 }
4167
4168 private:
4171 };
4172
4173 // --------------------------------------------------
4174 class SmTransfer : public SmBase<SmTransfer>
4175 {
4176 public:
4178
4180 std::shared_ptr<ChainStateTracker> const& chainstate,
4181 BridgeDef const& bridge,
4182 Transfer xfer)
4183 : Base(chainstate, bridge), xfer_(std::move(xfer))
4184 {
4185 }
4186
4187 [[nodiscard]] bool
4188 a2b() const
4189 {
4190 return xfer_.a2b;
4191 }
4192
4193 uint32_t
4195 {
4196 ChainStateTrack& st = destState();
4197
4198 st.env.tx(xchainCreateClaimId(xfer_.to, bridge_.jvb, bridge_.reward, xfer_.from))
4199 .close(); // needed for claim_id sequence to be
4200 // correct'
4201 st.spendFee(xfer_.to);
4202 return ++st.counters[&bridge_].claimId;
4203 }
4204
4205 void
4207 {
4208 ChainStateTrack& st = srcState();
4209 jtx::Account const& srcdoor = srcDoor();
4210
4211 if (xfer_.amt.asset() != xrpIssue())
4212 {
4213 st.env.tx(pay(srcdoor, xfer_.from, xfer_.amt));
4214 st.spendFee(srcdoor);
4215 }
4216 st.env.tx(xchainCommit(
4217 xfer_.from,
4218 bridge_.jvb,
4219 xfer_.claimId,
4220 xfer_.amt,
4221 xfer_.withClaim == WithClaim::Yes ? std::nullopt
4222 : std::optional<jtx::Account>(xfer_.finaldest)));
4223 st.spendFee(xfer_.from);
4224 st.transfer(xfer_.from, srcdoor, xfer_.amt);
4225 }
4226
4227 void
4229 {
4230 auto r = bridge_.reward;
4231 auto reward = divide(r, STAmount(bridge_.quorum), r.asset());
4232
4233 for (size_t i = 0; i < kNumSigners; ++i)
4234 {
4235 if (xfer_.attested[i])
4236 st.receive(bridge_.signers[i].account, reward);
4237 }
4238 st.spend(xfer_.to, reward, bridge_.quorum);
4239 }
4240
4241 bool
4243 {
4244 ChainStateTrack& st = destState();
4245
4246 // check all signers, but start at a random one
4247 for (size_t i = 0; i < kNumSigners; ++i)
4248 {
4249 size_t const signerIdx = (rnd + i) % kNumSigners;
4250 if (!(xfer_.attested[signerIdx]))
4251 {
4252 // enqueue one attestation for this signer
4253 xfer_.attested[signerIdx] = true;
4254
4255 st.signersAttns[signerIdx][&bridge_].xferClaims.emplace_back(claimAttestation(
4256 bridge_.signers[signerIdx].account,
4257 bridge_.jvb,
4258 xfer_.from,
4259 xfer_.amt,
4260 bridge_.signers[signerIdx].account,
4261 xfer_.a2b,
4262 xfer_.claimId,
4263 xfer_.withClaim == WithClaim::Yes
4264 ? std::nullopt
4266 bridge_.signers[signerIdx]));
4267 break;
4268 }
4269 }
4270
4271 // return true if quorum was reached, false otherwise
4272 bool const quorum =
4273 std::count(xfer_.attested.begin(), xfer_.attested.end(), true) >= bridge_.quorum;
4274 if (quorum && xfer_.withClaim == WithClaim::No)
4275 {
4276 distributeReward(st);
4277 st.transfer(dstDoor(), xfer_.finaldest, xfer_.amt);
4278 }
4279 return quorum;
4280 }
4281
4282 void
4284 {
4285 ChainStateTrack& st = destState();
4286 st.env.tx(
4287 xchainClaim(xfer_.to, bridge_.jvb, xfer_.claimId, xfer_.amt, xfer_.finaldest));
4288 distributeReward(st);
4289 st.transfer(dstDoor(), xfer_.finaldest, xfer_.amt);
4290 st.spendFee(xfer_.to);
4291 }
4292
4293 SmState
4295 {
4296 switch (smState_)
4297 {
4298 case SmState::Initial:
4299 xfer_.claimId = createClaimId();
4301 break;
4302
4304 commit();
4306 break;
4307
4308 case SmState::Attesting:
4309 if (attest(time, rnd))
4310 {
4313 }
4314 else
4315 {
4317 }
4318 break;
4319
4320 case SmState::Attested:
4321 assert(xfer_.withClaim == WithClaim::Yes);
4322 claim();
4324 break;
4325
4326 default:
4327 case SmState::Completed:
4328 assert(0); // should have been removed
4329 break;
4330 }
4331 return smState_;
4332 }
4333
4334 private:
4337 };
4338
4339 // --------------------------------------------------
4342
4344
4345 void
4347 uint64_t time,
4348 std::shared_ptr<ChainStateTracker> const& chainstate,
4349 BridgeDef const& bridge,
4350 Transfer transfer)
4351 {
4352 sm_.emplace_back(time, SmTransfer(chainstate, bridge, std::move(transfer)));
4353 }
4354
4355 void
4357 std::shared_ptr<ChainStateTracker> const& chainstate,
4358 BridgeDef const& bridge,
4360 {
4361 sm_.emplace_back(time, SmCreateAccount(chainstate, bridge, std::move(ac)));
4362 }
4363
4364public:
4365 void
4366 runSimulation(std::shared_ptr<ChainStateTracker> const& st, bool verifyBalances = true)
4367 {
4368 using namespace jtx;
4369 uint64_t time = 0;
4370 // NOLINTNEXTLINE(bugprone-random-generator-seed): fixed seed for reproducible test
4371 std::mt19937 gen(27); // Standard mersenne_twister_engine
4373
4374 while (!sm_.empty())
4375 {
4376 ++time;
4377 for (auto it = sm_.begin(); it != sm_.end();)
4378 {
4379 auto vis = [&](auto& sm) {
4380 uint32_t const rnd = distrib(gen);
4381 return sm.advance(time, rnd);
4382 };
4383 auto& [t, sm] = *it;
4384 if (t <= time && std::visit(vis, sm) == SmState::Completed)
4385 {
4386 it = sm_.erase(it);
4387 }
4388 else
4389 {
4390 ++it;
4391 }
4392 }
4393
4394 // send attestations
4395 st->sendAttestations();
4396
4397 // make sure all transactions have been applied
4398 st->a.env.close();
4399 st->b.env.close();
4400
4401 if (verifyBalances)
4402 {
4403 BEAST_EXPECT(st->verify());
4404 }
4405 }
4406 }
4407
4408 void
4410 {
4411 using namespace jtx;
4412
4413 testcase("Bridge usage simulation");
4414
4415 XEnv mcEnv(*this);
4416 XEnv scEnv(*this, true);
4417
4418 auto st = std::make_shared<ChainStateTracker>(mcEnv, scEnv);
4419
4420 // create 10 accounts + door funded on both chains, and store
4421 // in ChainStateTracker the initial amount of these accounts
4422 Account doorXRPLocking("doorXRPLocking"), doorUSDLocking("doorUSDLocking"),
4423 doorUSDIssuing("doorUSDIssuing");
4424
4425 static constexpr size_t kNumAcct = 10;
4426 auto a = [&doorXRPLocking, &doorUSDLocking, &doorUSDIssuing]() {
4427 using namespace std::literals;
4428 std::vector<Account> result;
4429 result.reserve(kNumAcct);
4430 for (int i = 0; i < kNumAcct; ++i)
4431 {
4432 result.emplace_back(
4433 "a"s + std::to_string(i), (i % 2) ? KeyType::Ed25519 : KeyType::Secp256k1);
4434 }
4435 result.emplace_back("doorXRPLocking");
4436 doorXRPLocking = result.back();
4437 result.emplace_back("doorUSDLocking");
4438 doorUSDLocking = result.back();
4439 result.emplace_back("doorUSDIssuing");
4440 doorUSDIssuing = result.back();
4441 return result;
4442 }();
4443
4444 for (auto& acct : a)
4445 {
4446 STAmount const amt{XRP(100000)};
4447
4448 mcEnv.fund(amt, acct);
4449 scEnv.fund(amt, acct);
4450 }
4451 Account const usdLockingAcc{"USDLocking"};
4452 IOU const usdLocking{usdLockingAcc["USD"]};
4453 IOU const usdIssuing{doorUSDIssuing["USD"]};
4454
4455 mcEnv.fund(XRP(100000), usdLockingAcc);
4456 mcEnv.close();
4457 mcEnv.tx(trust(doorUSDLocking, usdLocking(100000)));
4458 mcEnv.close();
4459 mcEnv.tx(pay(usdLockingAcc, doorUSDLocking, usdLocking(50000)));
4460
4461 for (int i = 0; i < a.size(); ++i)
4462 {
4463 auto& acct{a[i]};
4464 if (i < kNumAcct)
4465 {
4466 mcEnv.tx(trust(acct, usdLocking(100000)));
4467 scEnv.tx(trust(acct, usdIssuing(100000)));
4468 }
4469 st->init(acct);
4470 }
4471 for (auto& s : signers)
4472 st->init(s.account);
4473
4474 st->b.init(Account::kMaster);
4475
4476 // also create some unfunded accounts
4477 static constexpr size_t kNumUa = 20;
4478 auto ua = []() {
4479 using namespace std::literals;
4480 std::vector<Account> result;
4481 result.reserve(kNumUa);
4482 for (int i = 0; i < kNumUa; ++i)
4483 {
4484 result.emplace_back(
4485 "ua"s + std::to_string(i), (i % 2) ? KeyType::Ed25519 : KeyType::Secp256k1);
4486 }
4487 return result;
4488 }();
4489
4490 // initialize a bridge from a BridgeDef
4491 auto initBridge = [&mcEnv, &scEnv, &st](BridgeDef& bd) {
4492 bd.initBridge(mcEnv, scEnv);
4493 st->a.spendFee(bd.doorA, 2);
4494 st->b.spendFee(bd.doorB, 2);
4495 };
4496
4497 // create XRP -> XRP bridge
4498 // ------------------------
4499 BridgeDef xrpB{
4500 .doorA = doorXRPLocking,
4501 .issueA = xrpIssue(),
4502 .doorB = Account::kMaster,
4503 .issueB = xrpIssue(),
4504 .reward = XRP(1),
4505 .minAccountCreate = XRP(20),
4506 .quorum = quorum,
4507 .signers = signers,
4508 .jvb = json::ValueType::Null};
4509
4510 initBridge(xrpB);
4511
4512 // create USD -> USD bridge
4513 // ------------------------
4514 BridgeDef usdB{
4515 .doorA = doorUSDLocking,
4516 .issueA = usdLocking,
4517 .doorB = doorUSDIssuing,
4518 .issueB = usdIssuing,
4519 .reward = XRP(1),
4520 .minAccountCreate = XRP(20),
4521 .quorum = quorum,
4522 .signers = signers,
4523 .jvb = json::ValueType::Null};
4524
4525 initBridge(usdB);
4526
4527 // try a single account create + transfer to validate the simulation
4528 // engine. Do the transfer 8 time steps after the account create, to
4529 // give time enough for ua[0] to be funded now so it can reserve
4530 // the claimID
4531 // -----------------------------------------------------------------
4532 ac(0,
4533 st,
4534 xrpB,
4535 {.from = a[0], .to = ua[0], .amt = XRP(777), .reward = xrpB.reward, .a2b = true});
4536 xfer(
4537 8,
4538 st,
4539 xrpB,
4540 {.from = a[0], .to = ua[0], .finaldest = a[2], .amt = XRP(3), .a2b = true});
4541 runSimulation(st);
4542
4543 // try the same thing in the other direction
4544 // -----------------------------------------
4545 ac(0,
4546 st,
4547 xrpB,
4548 {.from = a[0], .to = ua[0], .amt = XRP(777), .reward = xrpB.reward, .a2b = false});
4549 xfer(
4550 8,
4551 st,
4552 xrpB,
4553 {.from = a[0], .to = ua[0], .finaldest = a[2], .amt = XRP(3), .a2b = false});
4554 runSimulation(st);
4555
4556 // run multiple XRP transfers
4557 // --------------------------
4558 xfer(
4559 0,
4560 st,
4561 xrpB,
4562 {.from = a[0],
4563 .to = a[0],
4564 .finaldest = a[1],
4565 .amt = XRP(6),
4566 .a2b = true,
4567 .withClaim = WithClaim::No});
4568 xfer(
4569 1,
4570 st,
4571 xrpB,
4572 {.from = a[0],
4573 .to = a[0],
4574 .finaldest = a[1],
4575 .amt = XRP(8),
4576 .a2b = false,
4577 .withClaim = WithClaim::No});
4578 xfer(
4579 1, st, xrpB, {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(1), .a2b = true});
4580 xfer(
4581 2,
4582 st,
4583 xrpB,
4584 {.from = a[0], .to = a[0], .finaldest = a[1], .amt = XRP(3), .a2b = false});
4585 xfer(
4586 2,
4587 st,
4588 xrpB,
4589 {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(5), .a2b = false});
4590 xfer(
4591 2,
4592 st,
4593 xrpB,
4594 {.from = a[0],
4595 .to = a[0],
4596 .finaldest = a[1],
4597 .amt = XRP(7),
4598 .a2b = false,
4599 .withClaim = WithClaim::No});
4600 xfer(
4601 2, st, xrpB, {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(9), .a2b = true});
4602 runSimulation(st);
4603
4604 // run one USD transfer
4605 // --------------------
4606 xfer(
4607 0,
4608 st,
4609 usdB,
4610 {.from = a[0], .to = a[1], .finaldest = a[2], .amt = usdLocking(3), .a2b = true});
4611 runSimulation(st);
4612
4613 // run multiple USD transfers
4614 // --------------------------
4615 xfer(
4616 0,
4617 st,
4618 usdB,
4619 {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdLocking(6), .a2b = true});
4620 xfer(
4621 1,
4622 st,
4623 usdB,
4624 {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdIssuing(8), .a2b = false});
4625 xfer(
4626 1,
4627 st,
4628 usdB,
4629 {.from = a[1], .to = a[1], .finaldest = a[1], .amt = usdLocking(1), .a2b = true});
4630 xfer(
4631 2,
4632 st,
4633 usdB,
4634 {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdIssuing(3), .a2b = false});
4635 xfer(
4636 2,
4637 st,
4638 usdB,
4639 {.from = a[1], .to = a[1], .finaldest = a[1], .amt = usdIssuing(5), .a2b = false});
4640 xfer(
4641 2,
4642 st,
4643 usdB,
4644 {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdIssuing(7), .a2b = false});
4645 xfer(
4646 2,
4647 st,
4648 usdB,
4649 {.from = a[1], .to = a[1], .finaldest = a[1], .amt = usdLocking(9), .a2b = true});
4650 runSimulation(st);
4651
4652 // run mixed transfers
4653 // -------------------
4654 xfer(
4655 0, st, xrpB, {.from = a[0], .to = a[0], .finaldest = a[0], .amt = XRP(1), .a2b = true});
4656 xfer(
4657 0,
4658 st,
4659 usdB,
4660 {.from = a[1], .to = a[3], .finaldest = a[3], .amt = usdIssuing(3), .a2b = false});
4661 xfer(
4662 0,
4663 st,
4664 usdB,
4665 {.from = a[3], .to = a[2], .finaldest = a[1], .amt = usdIssuing(5), .a2b = false});
4666
4667 xfer(
4668 1,
4669 st,
4670 xrpB,
4671 {.from = a[0], .to = a[0], .finaldest = a[0], .amt = XRP(4), .a2b = false});
4672 xfer(
4673 1, st, xrpB, {.from = a[1], .to = a[1], .finaldest = a[0], .amt = XRP(8), .a2b = true});
4674 xfer(
4675 1,
4676 st,
4677 usdB,
4678 {.from = a[4], .to = a[1], .finaldest = a[1], .amt = usdLocking(7), .a2b = true});
4679
4680 xfer(
4681 3, st, xrpB, {.from = a[1], .to = a[1], .finaldest = a[0], .amt = XRP(7), .a2b = true});
4682 xfer(
4683 3,
4684 st,
4685 xrpB,
4686 {.from = a[0], .to = a[4], .finaldest = a[3], .amt = XRP(2), .a2b = false});
4687 xfer(
4688 3, st, xrpB, {.from = a[1], .to = a[1], .finaldest = a[0], .amt = XRP(9), .a2b = true});
4689 xfer(
4690 3,
4691 st,
4692 usdB,
4693 {.from = a[3], .to = a[1], .finaldest = a[1], .amt = usdIssuing(11), .a2b = false});
4694 runSimulation(st);
4695
4696 // run multiple account create to stress attestation batching
4697 // ----------------------------------------------------------
4698 ac(0,
4699 st,
4700 xrpB,
4701 {.from = a[0], .to = ua[1], .amt = XRP(301), .reward = xrpB.reward, .a2b = true});
4702 ac(0,
4703 st,
4704 xrpB,
4705 {.from = a[1], .to = ua[2], .amt = XRP(302), .reward = xrpB.reward, .a2b = true});
4706 ac(1,
4707 st,
4708 xrpB,
4709 {.from = a[0], .to = ua[3], .amt = XRP(303), .reward = xrpB.reward, .a2b = true});
4710 ac(2,
4711 st,
4712 xrpB,
4713 {.from = a[1], .to = ua[4], .amt = XRP(304), .reward = xrpB.reward, .a2b = true});
4714 ac(3,
4715 st,
4716 xrpB,
4717 {.from = a[0], .to = ua[5], .amt = XRP(305), .reward = xrpB.reward, .a2b = true});
4718 ac(4,
4719 st,
4720 xrpB,
4721 {.from = a[1], .to = ua[6], .amt = XRP(306), .reward = xrpB.reward, .a2b = true});
4722 ac(6,
4723 st,
4724 xrpB,
4725 {.from = a[0], .to = ua[7], .amt = XRP(307), .reward = xrpB.reward, .a2b = true});
4726 ac(7,
4727 st,
4728 xrpB,
4729 {.from = a[2], .to = ua[8], .amt = XRP(308), .reward = xrpB.reward, .a2b = true});
4730 ac(9,
4731 st,
4732 xrpB,
4733 {.from = a[0], .to = ua[9], .amt = XRP(309), .reward = xrpB.reward, .a2b = true});
4734 ac(9,
4735 st,
4736 xrpB,
4737 {.from = a[0], .to = ua[9], .amt = XRP(309), .reward = xrpB.reward, .a2b = true});
4738 ac(10,
4739 st,
4740 xrpB,
4741 {.from = a[0], .to = ua[10], .amt = XRP(310), .reward = xrpB.reward, .a2b = true});
4742 ac(12,
4743 st,
4744 xrpB,
4745 {.from = a[0], .to = ua[11], .amt = XRP(311), .reward = xrpB.reward, .a2b = true});
4746 ac(12,
4747 st,
4748 xrpB,
4749 {.from = a[3], .to = ua[12], .amt = XRP(312), .reward = xrpB.reward, .a2b = true});
4750 ac(12,
4751 st,
4752 xrpB,
4753 {.from = a[4], .to = ua[13], .amt = XRP(313), .reward = xrpB.reward, .a2b = true});
4754 ac(12,
4755 st,
4756 xrpB,
4757 {.from = a[3], .to = ua[14], .amt = XRP(314), .reward = xrpB.reward, .a2b = true});
4758 ac(12,
4759 st,
4760 xrpB,
4761 {.from = a[6], .to = ua[15], .amt = XRP(315), .reward = xrpB.reward, .a2b = true});
4762 ac(13,
4763 st,
4764 xrpB,
4765 {.from = a[7], .to = ua[16], .amt = XRP(316), .reward = xrpB.reward, .a2b = true});
4766 ac(15,
4767 st,
4768 xrpB,
4769 {.from = a[3], .to = ua[17], .amt = XRP(317), .reward = xrpB.reward, .a2b = true});
4770 runSimulation(st, true); // balances verification working now.
4771 }
4772
4773 void
4774 run() override
4775 {
4777 }
4778};
4779
4782
4783} // namespace xrpl::test
T all_of(T... args)
T apply(T... args)
T back(T... args)
A testsuite class.
Definition suite.h:50
void pass()
Record a successful test condition.
Definition suite.h:500
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Represents a JSON value.
Definition json_value.h:130
A currency issued by an account.
Definition Issue.h:13
Asset const & asset() const
Definition STAmount.h:478
std::shared_ptr< STLedgerEntry const > const_pointer
static constexpr TERSubset fromInt(int from)
Definition TER.h:431
static Account const kMaster
The master account that holds all XRP in genesis.
SmBase(std::shared_ptr< ChainStateTracker > const &chainstate, BridgeDef const &bridge)
SmCreateAccount(std::shared_ptr< ChainStateTracker > const &chainstate, BridgeDef const &bridge, AccountCreate create)
void attest(uint64_t time, uint32_t rnd)
SmState advance(uint64_t time, uint32_t rnd)
SmState advance(uint64_t time, uint32_t rnd)
void distributeReward(ChainStateTrack &st)
bool attest(uint64_t time, uint32_t rnd)
SmTransfer(std::shared_ptr< ChainStateTracker > const &chainstate, BridgeDef const &bridge, Transfer xfer)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
A transaction testing environment.
Definition Env.h:143
TER ter() const
Return the TER for the last JTx.
Definition Env.h:675
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:284
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:201
void memoize(Account const &account)
Associate AccountID with account.
Definition Env.cpp:174
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:353
Set the fee on a JTx.
Definition fee.h:15
Converts to IOU Issue or STAmount.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:13
Set the flags on a JTx.
Definition txflags.h:9
T clear(T... args)
T count(T... args)
T emplace_back(T... args)
T forward(T... args)
T front(T... args)
T make_pair(T... args)
T make_shared(T... args)
Severity
Severity level / threshold of a Journal message.
Definition Journal.h:11
@ Null
'null' value
Definition json_value.h:19
STL namespace.
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition Indexes.cpp:471
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:495
Keylet xChainClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:481
Check operations.
Definition check.h:12
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 pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
json::Value regkey(Account const &account, DisabledT)
Disable the regular key.
Definition regkey.cpp:13
std::vector< json::Value > JValueVec
json::Value sidechainXchainAccountCreate(Account const &acc, json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value xchainCommit(Account const &acc, json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, std::optional< Account > const &dst)
FeatureBitset testableAmendments()
Definition Env.h:76
json::Value bridgeCreate(Account const &acc, json::Value const &bridge, STAmount const &reward, std::optional< STAmount > const &minAccountCreate)
json::Value bridge(Account const &lockingChainDoor, Issue const &lockingChainIssue, Account const &issuingChainDoor, Issue const &issuingChainIssue)
constexpr std::size_t kUtXchainDefaultNumSigners
json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
JValueVec claimAttestations(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 claimAttestation(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)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:28
json::Value createAccountAttestation(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)
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
json::Value xchainClaim(Account const &acc, json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, Account const &dst)
constexpr std::size_t kUtXchainDefaultQuorum
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value bridgeModify(Account const &acc, json::Value const &bridge, std::optional< STAmount > const &reward, std::optional< STAmount > const &minAccountCreate)
json::Value xchainCreateClaimId(Account const &acc, json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
Definition multisign.cpp:31
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:48
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_LINE
Definition TER.h:211
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
std::string transToken(TER code)
Definition TER.cpp:247
@ temBAD_ISSUER
Definition TER.h:79
@ temINVALID_FLAG
Definition TER.h:97
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition TER.h:121
@ temMALFORMED
Definition TER.h:73
@ temXCHAIN_BRIDGE_NONDOOR_OWNER
Definition TER.h:120
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition TER.h:119
@ temDISABLED
Definition TER.h:100
@ temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
Definition TER.h:122
@ temBAD_AMOUNT
Definition TER.h:75
@ temXCHAIN_EQUAL_DOOR_ACCOUNTS
Definition TER.h:117
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition TER.h:344
@ tecUNFUNDED_PAYMENT
Definition TER.h:283
@ tecNO_ENTRY
Definition TER.h:304
@ tecXCHAIN_NO_SIGNERS_LIST
Definition TER.h:342
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition TER.h:345
@ tecXCHAIN_NO_CLAIM_ID
Definition TER.h:335
@ tecXCHAIN_BAD_CLAIM_ID
Definition TER.h:336
@ tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR
Definition TER.h:349
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition TER.h:350
@ tecINSUFFICIENT_RESERVE
Definition TER.h:305
@ tecXCHAIN_SELF_COMMIT
Definition TER.h:348
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition TER.h:337
@ tecXCHAIN_REWARD_MISMATCH
Definition TER.h:341
@ tecNO_PERMISSION
Definition TER.h:303
@ tecDST_TAG_NEEDED
Definition TER.h:307
@ tecNO_ISSUER
Definition TER.h:297
@ tecDUPLICATE
Definition TER.h:313
@ tecHAS_OBLIGATIONS
Definition TER.h:315
@ tecNO_DST
Definition TER.h:288
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
BaseUInt< 256 > uint256
Definition base_uint.h:562
@ tesSUCCESS
Definition TER.h:240
T reserve(T... args)
T size(T... args)
std::vector< balance > rewardAccounts
bool checkMostBalances(STAmount const &amt, STAmount const &reward)
bool hasHappened(STAmount const &amt, STAmount const &reward, bool checkPayer=true)
BalanceTransfer(T &env, jtx::Account const &fromAcct, jtx::Account const &toAcct, jtx::Account const &payer, std::vector< jtx::Account > const &payees, bool withClaim)
bool payeesReceived(STAmount const &reward) const
BalanceTransfer(T &env, jtx::Account const &fromAcct, jtx::Account const &toAcct, jtx::Account const &payer, jtx::Account const *payees, size_t numPayees, bool withClaim)
STAmount diff() const
Balance(T &env, jtx::Account const &account)
jtx::Account const & account
void initBridge(ENV &mcEnv, ENV &scEnv)
std::vector< jtx::Signer > const & signers
SEnv & enableFeature(uint256 const feature)
STAmount balance(jtx::Account const &account, Issue const &issue) const
std::uint64_t claimID(json::Value const &jvb)
SLE::const_pointer account(jtx::Account const &account)
SEnv & tx(JsonValue &&jv, FN const &... fN)
XRPAmount reserve(std::uint32_t count)
std::uint64_t claimCount(json::Value const &jvb)
XRPAmount txFee()
SEnv & fund(STAmount const &amount, Arg const &arg, Args const &... args)
SEnv & disableFeature(uint256 const feature)
SEnv(T &s, std::unique_ptr< Config > config, FeatureBitset features, std::unique_ptr< Logs > logs=nullptr, beast::Severity thresh=beast::Severity::Error)
SLE::const_pointer bridge(json::Value const &jvb)
SLE::const_pointer caClaimID(json::Value const &jvb, std::uint64_t seq)
STAmount balance(jtx::Account const &account) const
SEnv & multiTx(jtx::JValueVec const &jvv, FN const &... fN)
SLE::const_pointer claimID(json::Value const &jvb, std::uint64_t seq)
std::array< bool, kNumSigners > attested
bool verify(ENV &env, jtx::Account const &acct) const
void init(ENV &env, jtx::Account const &acct)
std::function< void(std::vector< size_t > const &signers)> complete_cb
std::map< BridgeID, BridgeCounters > counters
void spend(jtx::Account const &acct, STAmount amt, std::uint64_t times=1)
void sendAttestations(size_t signerIdx, BridgeID bridge, ClaimVec &claims)
void spendFee(jtx::Account const &acct, size_t times=1)
std::map< uint32_t, CreateClaimVec > CreateClaimMap
std::map< jtx::Account, AccountStateTrack > accounts
void transfer(jtx::Account const &from, jtx::Account const &to, STAmount amt)
void init(jtx::Account const &acct)
std::unordered_map< BridgeID, Claims > SignerAttns
uint32_t sendCreateAttestations(size_t signerIdx, BridgeID bridge, CreateClaimVec &claims)
std::array< SignerAttns, kNumSigners > SignersAttns
void receive(jtx::Account const &acct, STAmount amt, std::uint64_t divisor=1)
std::array< bool, kNumSigners > attested
std::variant< SmCreateAccount, SmTransfer > Sm
void xfer(uint64_t time, std::shared_ptr< ChainStateTracker > const &chainstate, BridgeDef const &bridge, Transfer transfer)
XEnv< XChainSim_test > ENV
static constexpr size_t kNumSigners
void ac(uint64_t time, std::shared_ptr< ChainStateTracker > const &chainstate, BridgeDef const &bridge, AccountCreate ac)
void run() override
Runs the suite.
void runSimulation(std::shared_ptr< ChainStateTracker > const &st, bool verifyBalances=true)
std::list< std::pair< uint64_t, Sm > > SmCont
void run() override
Runs the suite.
XRPAmount reserve(std::uint32_t count)
void testXChainAddClaimNonBatchAttestation()
void testXChainAddAccountCreateNonBatchAttestation()
XEnv(T &s, bool side=false)
A signer in a SignerList.
Definition multisign.h:17
std::vector< Signer > const altSigners
std::vector< Account > const payees
json::Value createBridge(Account const &acc, json::Value const &bridge=json::ValueType::Null, STAmount const &reward=XRP(1), std::optional< STAmount > const &minAccountCreate=std::nullopt) const
std::vector< Signer > const signers
JValueVec attCreateAcctVec(std::uint64_t createCount, jtx::AnyAmount const &amt, jtx::Account const &dst, std::size_t const numAtts, std::size_t const fromIdx=0)
T time(T... args)
T tmpnam(T... args)
T to_string(T... args)
T visit(T... args)