1#include <test/jtx/AMM.h>
2#include <test/jtx/AMMTest.h>
3#include <test/jtx/CaptureLogs.h>
4#include <test/jtx/Env.h>
5#include <test/jtx/TestHelpers.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/envconfig.h>
8#include <test/jtx/escrow.h>
9#include <test/jtx/fee.h>
10#include <test/jtx/flags.h>
11#include <test/jtx/offer.h>
12#include <test/jtx/paths.h>
13#include <test/jtx/pay.h>
14#include <test/jtx/rate.h>
15#include <test/jtx/sendmax.h>
16#include <test/jtx/seq.h>
17#include <test/jtx/sig.h>
18#include <test/jtx/tags.h>
19#include <test/jtx/ter.h>
20#include <test/jtx/trust.h>
21#include <test/jtx/txflags.h>
23#include <xrpl/basics/Number.h>
24#include <xrpl/basics/base_uint.h>
25#include <xrpl/basics/chrono.h>
26#include <xrpl/basics/safe_cast.h>
27#include <xrpl/beast/unit_test/suite.h>
28#include <xrpl/beast/utility/Journal.h>
29#include <xrpl/json/json_value.h>
30#include <xrpl/ledger/ApplyView.h>
31#include <xrpl/ledger/helpers/AMMHelpers.h>
32#include <xrpl/ledger/helpers/AccountRootHelpers.h>
33#include <xrpl/protocol/AMMCore.h>
34#include <xrpl/protocol/AccountID.h>
35#include <xrpl/protocol/AmountConversions.h>
36#include <xrpl/protocol/Feature.h>
37#include <xrpl/protocol/Indexes.h>
38#include <xrpl/protocol/Issue.h>
39#include <xrpl/protocol/LedgerFormats.h>
40#include <xrpl/protocol/Protocol.h>
41#include <xrpl/protocol/Quality.h>
42#include <xrpl/protocol/Rules.h>
43#include <xrpl/protocol/SField.h>
44#include <xrpl/protocol/STAmount.h>
45#include <xrpl/protocol/TER.h>
46#include <xrpl/protocol/TxFlags.h>
47#include <xrpl/protocol/UintTypes.h>
48#include <xrpl/protocol/jss.h>
49#include <xrpl/tx/Transactor.h>
50#include <xrpl/tx/transactors/dex/AMMBid.h>
52#include <boost/regex/v5/regex.hpp>
53#include <boost/regex/v5/regex_search.hpp>
108 [&](
AMM& ammAlice,
Env&) {
120 [&](
AMM& ammAlice,
Env&) {
131 [&](
AMM& ammAlice,
Env&) {
134 {{
USD(20'000),
BTC(0.5)}});
154 env(
fset(
gw_, asfRequireAuth));
172 env(
fset(
gw_, asfGlobalFreeze));
183 BEAST_EXPECT(
amm.expectTradingFee(1'000));
184 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
210 BEAST_EXPECT(!ammAlice.ammExists());
218 BEAST_EXPECT(!ammAlice.ammExists());
226 BEAST_EXPECT(!ammAlice.ammExists());
240 BEAST_EXPECT(!ammAlice.ammExists());
248 BEAST_EXPECT(!ammAlice.ammExists());
256 BEAST_EXPECT(!ammAlice.ammExists());
275 BEAST_EXPECT(!ammAlice.ammExists());
299 BEAST_EXPECT(!ammAlice.ammExists());
327 env(
fset(
gw_, asfRequireAuth));
340 env(
fset(
gw_, asfGlobalFreeze));
344 for (
auto const& account : {
alice_,
gw_})
347 BEAST_EXPECT(!
amm.ammExists());
369 auto const startingXrp =
XRP(1'000) +
reserve(env, 3) + env.
current()->fees().base * 4;
384 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 5;
421 AMM const ammAMMToken(
427 AMM const ammAMMToken1(
439 auto const token1 = ammAlice.
lptIssue();
440 auto const token2 = ammAlice1.lptIssue();
441 AMM const ammAMMTokens(
461 env(
fclear(gw1, asfDefaultRipple));
464 auto const usD1 = gw1[
"USD"];
492 {tfLPToken, 1'000, std::nullopt,
USD(100), std::nullopt, std::nullopt},
493 {tfLPToken, 1'000,
XRP(100), std::nullopt, std::nullopt, std::nullopt},
494 {tfLPToken, 1'000, std::nullopt, std::nullopt,
STAmount{
USD, 1, -1}, std::nullopt},
501 {tfLPToken, 1'000,
XRP(100), std::nullopt,
STAmount{
USD, 1, -1}, std::nullopt},
502 {tfLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, 1'000},
503 {tfSingleAsset, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
504 {tfSingleAsset, std::nullopt, std::nullopt,
USD(100), std::nullopt, std::nullopt},
511 {tfSingleAsset, std::nullopt,
USD(100), std::nullopt, std::nullopt, 1'000},
512 {tfTwoAsset, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
513 {tfTwoAsset, std::nullopt,
XRP(100),
USD(100),
STAmount{
USD, 1, -1}, std::nullopt},
514 {tfTwoAsset, std::nullopt,
XRP(100), std::nullopt, std::nullopt, std::nullopt},
515 {tfTwoAsset, std::nullopt,
XRP(100),
USD(100), std::nullopt, 1'000},
522 {tfOneAssetLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
523 {tfOneAssetLPToken, std::nullopt,
XRP(100),
USD(100), std::nullopt, std::nullopt},
530 {tfOneAssetLPToken, 1'000,
XRP(100), std::nullopt, std::nullopt, 1'000},
531 {tfLimitLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
532 {tfLimitLPToken, 1'000,
USD(100), std::nullopt, std::nullopt, std::nullopt},
533 {tfLimitLPToken, std::nullopt,
USD(100),
XRP(100), std::nullopt, std::nullopt},
534 {tfLimitLPToken, std::nullopt,
XRP(100), std::nullopt,
STAmount{
USD, 1, -1}, 1'000},
535 {tfTwoAssetIfEmpty, std::nullopt, std::nullopt, std::nullopt, std::nullopt, 1'000},
536 {tfTwoAssetIfEmpty, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
543 {tfTwoAssetIfEmpty | tfLPToken,
549 for (
auto const& it : invalidOptions)
567 jv[jss::Account] =
alice_.human();
568 jv[jss::TransactionType] = jss::AMMDeposit;
582 jv[jss::Account] =
alice_.human();
583 jv[jss::TransactionType] = jss::AMMDeposit;
587 jv[jss::Flags] = tfLPToken;
726 [&](
AMM& ammAlice,
Env& env) {
727 auto const enabledV13 = env.
current()->rules().enabled(fixAMMv1_3);
747 {features, features - fixAMMv1_3});
757 [&](
AMM& ammAlice,
Env& env) {
758 env(
fset(
gw_, asfGlobalFreeze));
759 auto const freezeBlocksAll =
760 features[featureAMMClawback] || features[fixCleanup3_3_0];
761 if (!freezeBlocksAll)
775 for (
auto const& account : {
carol_,
gw_})
785 account, 1'000'000, std::nullopt, std::nullopt,
Ter(
tecFROZEN));
797 [&](
AMM& ammAlice,
Env& env) {
800 auto const freezeBlocksAll =
801 features[featureAMMClawback] || features[fixCleanup3_3_0];
802 if (!freezeBlocksAll)
829 if (!features[fixCleanup3_3_0])
849 [&](
AMM& ammAlice,
Env& env) {
868 {{
USD(20'000),
BTC(0.5)}});
872 Env env(*
this, features);
874 env(
fset(
gw_, asfRequireAuth));
887 if (features[featureAMMClawback] || features[fixCleanup3_3_0])
959 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 4;
993 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 4;
1160 {.pool = {{
USD(1'000'000),
XRP(1'000'000)}}, .features = {features - fixAMMv1_3}});
1200 using namespace jtx;
1204 auto const baseFee = env.
current()->fees().base;
1217 for (
Number const& deltaLPTokens :
1218 {
Number{UINT64_C(100000'0000000009), -10},
Number{UINT64_C(100000'0000000001), -10}})
1223 IOUAmount const newLPTokens{deltaLPTokens};
1235 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1236 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1240 Number const fr = deltaLPTokens / 1e7;
1244 Number const deltaXRP = fr * 1e10;
1245 Number const deltaUSD = fr * 1e4;
1253 XRP(10'000) + depositXRP,
1254 USD(10'000) + depositUSD,
1369 STAmount{USD, UINT64_C(10'000'000001), -6},
1389 for (
auto const& feat : {all, all - fixAMMv1_3})
1391 Env env(*
this, feat);
1482 using namespace jtx;
1485 [&](
AMM& ammAlice,
Env& env) {
1487 .asset1Out =
XRP(100),
1495 [&](
AMM& ammAlice,
Env& env) {
1497 .asset1Out =
USD(100),
1508 env(
fset(
gw_, asfRequireAuth));
1521 .asset1Out =
USD(100),
1557 NotTEC>>
const invalidOptions = {
1569 tfSingleAsset | tfTwoAsset,
1571 {1'000, std::nullopt, std::nullopt, std::nullopt, tfWithdrawAll,
temMALFORMED},
1576 tfWithdrawAll | tfLPToken,
1578 {std::nullopt, std::nullopt,
USD(100), std::nullopt, tfWithdrawAll,
temMALFORMED},
1583 tfWithdrawAll | tfOneAssetWithdrawAll,
1585 {std::nullopt,
USD(100), std::nullopt, std::nullopt, tfWithdrawAll,
temMALFORMED},
1590 tfOneAssetWithdrawAll,
1592 {1'000, std::nullopt,
USD(100), std::nullopt, std::nullopt,
temMALFORMED},
1609 for (
auto const& it : invalidOptions)
1620 Ter(std::get<5>(it)));
1652 tfOneAssetWithdrawAll,
1701 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
1708 [&](
AMM& ammAlice,
Env& env) {
1718 alice_,
IOUAmount{9'999'999'9999, -4}, std::nullopt, std::nullopt, err);
1728 {all, all - fixAMMv1_3});
1731 [&](
AMM& ammAlice,
Env& env) {
1740 alice_,
IOUAmount{9'999'999'999999999, -9}, std::nullopt, std::nullopt, err);
1750 {all, all - fixAMMv1_3});
1760 [&](
AMM& ammAlice,
Env& env) {
1761 auto const fix330 = env.
current()->rules().enabled(fixCleanup3_3_0);
1762 ammAlice.
deposit({.account =
gw_, .asset1In =
USD(1'000), .asset2In =
XRP(1'000)});
1763 env(
fset(
gw_, asfGlobalFreeze));
1766 for (
auto const& account : {
alice_,
gw_})
1771 auto const frozenErr =
1773 ammAlice.
withdraw(account,
USD(100), std::nullopt, std::nullopt, frozenErr);
1774 ammAlice.
withdraw(account, 1'000, std::nullopt, std::nullopt, frozenErr);
1784 [&](
AMM& ammAlice,
Env& env) {
1785 auto const fix330 = env.
current()->rules().enabled(fixCleanup3_3_0);
1793 ammAlice.
withdraw(
alice_, 1'000, std::nullopt, std::nullopt, indivFreezeErr);
1794 ammAlice.
withdraw(
alice_,
USD(100), std::nullopt, std::nullopt, indivFreezeErr);
1828 [&](
AMM& ammAlice,
Env& env) {
1837 {all, all - fixAMMv1_3});
1857 [&](
AMM& ammAlice,
Env&) {
1865 [&](
AMM& ammAlice,
Env&) {
1887 [&](
AMM& ammAlice,
Env& env) {
1893 auto const err = [&] {
1896 if (env.
enabled(fixCleanup3_3_0))
1907 {.features = {all, all - fixAMMv1_3, all - fixCleanup3_3_0}, .noLog =
true});
1951 using namespace jtx;
1956 auto const baseFee = env.
current()->fees().base.drops();
2001 [&](
AMM& ammAlice,
Env& env) {
2017 {all, all - fixAMMv1_3});
2065 [&](
AMM& ammAlice,
Env& env) {
2090 {all, all - fixAMMv1_3});
2132 [&](
AMM& ammAlice,
Env& env) {
2150 else if (env.
enabled(fixAMMv1_3))
2160 {.features = {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3}, .noLog =
true});
2164 [&](
AMM& ammAlice,
Env& env) {
2182 else if (env.
enabled(fixAMMv1_3))
2193 {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3});
2226 STAmount{USD, UINT64_C(9'999'999999), -6},
2230 [&](
AMM& ammAlice,
Env& env) {
2247 {all, all - fixAMMv1_3});
2296 [&](
AMM& ammAlice,
Env& env) {
2297 auto const err = env.
enabled(fixCleanup3_3_0) || env.
enabled(fixCleanup3_4_0)
2303 .asset1Out =
USD(0),
2310 {all - fixCleanup3_3_0 - fixCleanup3_4_0, all - fixCleanup3_4_0, all});
2317 using namespace jtx;
2331 std::nullopt, 1'001, std::nullopt, std::nullopt, std::nullopt,
Ter(
temBAD_FEE));
2364 using namespace jtx;
2369 ammAlice.
vote({}, 1'000);
2375 auto vote = [&](
AMM& ammAlice,
2378 int fundUSD = 100'000,
2388 fund(env,
gw_, {a}, {
USD(fundUSD)}, Fund::Acct);
2390 ammAlice.
vote(a, 50 * (i + 1));
2397 [&](
AMM& ammAlice,
Env& env) {
2398 for (
int i = 0; i < 7; ++i)
2399 vote(ammAlice, env, i, 10'000);
2410 for (
int i = 0; i < 7; ++i)
2411 vote(ammAlice, env, i);
2414 ammAlice.
vote(a, 450);
2421 for (
int i = 0; i < 7; ++i)
2422 vote(ammAlice, env, i);
2424 vote(ammAlice, env, 7, 100'000, 20'000'000);
2431 for (
int i = 7; i > 0; --i)
2432 vote(ammAlice, env, i);
2434 vote(ammAlice, env, 0, 100'000, 20'000'000);
2443 for (
int i = 0; i < 7; ++i)
2444 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2446 for (
int i = 0; i < 7; ++i)
2460 for (
int i = 0; i < 7; ++i)
2461 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2463 for (
int i = 0; i < 7; ++i)
2464 ammAlice.
withdraw(accounts[i], 9'000'000);
2468 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2469 for (
auto const& entry : info)
2470 BEAST_EXPECT(entry[jss::account] !=
carol_.human());
2480 using namespace jtx;
2490 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2497 .bidMin = 1'000'000,
2509 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2516 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2522 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2528 BEAST_EXPECT(
Number{
amm.getLPTokensBalance(
gw_)} == 1);
2545 .flags = tfWithdrawAll,
2551 for (
auto bid : {0, -100})
2600 .authAccounts = {bob_},
2608 .assets = {{USD, GBP}},
2615 .bidMax = STAmount{USD, 100},
2617 Ter(temBAD_AMM_TOKENS));
2620 .bidMin = STAmount{USD, 100},
2622 Ter(temBAD_AMM_TOKENS));
2626 testAMM([&](AMM& ammAlice, Env& env) {
2627 ammAlice.withdrawAll(alice_);
2636 testAMM([&](AMM& ammAlice, Env& env) {
2641 env.fund(
XRP(1'000), bob_, ed, bill, scott, james);
2643 ammAlice.deposit(carol_, 1'000'000);
2647 .authAccounts = {bob_, ed, bill, scott, james},
2653 testAMM([&](AMM& ammAlice, Env& env) {
2654 fund(env, gw_, {bob_},
XRP(1'000), {USD(100)}, Fund::Acct);
2655 ammAlice.deposit(carol_, 1'000'000);
2656 ammAlice.deposit(bob_, 10);
2659 .bidMin = 1'000'001,
2661 Ter(tecAMM_INVALID_TOKENS));
2664 .bidMax = 1'000'001,
2666 Ter(tecAMM_INVALID_TOKENS));
2671 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2676 Ter(tecAMM_INVALID_TOKENS));
2682 fund(env, gw_, {alice_, bob_},
XRP(1'000), {USD(1'000)});
2683 AMM
amm(env, gw_,
XRP(10), USD(1'000));
2684 auto const lpIssue =
amm.lptIssue();
2685 env.trust(STAmount{lpIssue, 100}, alice_);
2686 env.trust(STAmount{lpIssue, 50}, bob_);
2687 env(
pay(gw_, alice_, STAmount{lpIssue, 100}));
2688 env(
pay(gw_, bob_, STAmount{lpIssue, 50}));
2689 env(
amm.bid({.account = alice_, .bidMin = 100}));
2696 Ter(tecAMM_FAILED));
2704 using namespace jtx;
2711 [&](
AMM& ammAlice,
Env& env) {
2713 env(ammAlice.
bid({.account = carol_, .bidMin = 110}));
2726 [&](
AMM& ammAlice,
Env& env) {
2729 env(ammAlice.
bid({.account = carol_, .bidMin = 110, .bidMax = 110}));
2734 env(ammAlice.
bid({.account = alice_, .bidMin = 180, .bidMax = 200}));
2746 [&](
AMM& ammAlice,
Env& env) {
2749 env(ammAlice.
bid({.account = carol_, .bidMin = 110}));
2755 env(ammAlice.
bid({.account = bob_}));
2765 env(ammAlice.
bid({.account = carol_, .bidMax = 600}));
2777 env(ammAlice.
bid({.account = carol_, .bidMin = 100, .bidMax = 600}));
2787 [&](
AMM& ammAlice,
Env& env) {
2792 if (!features[fixAMMv1_3])
2804 env(ammAlice.
bid({.account = carol_, .bidMin = 110})).
close();
2808 env(ammAlice.
bid({.account = bob_}));
2813 env(ammAlice.
bid({.account = carol_}));
2818 env(ammAlice.
bid({.account = bob_}));
2823 env(ammAlice.
bid({.account = carol_, .bidMin = 110})).
close();
2826 if (!features[fixAMMv1_3])
2847 [&](
AMM& ammAlice,
Env& env) {
2852 ammAlice.
deposit(ed, 1'000'000);
2854 ammAlice.
deposit(dan, 500'000);
2859 .authAccounts = {bob_, ed},
2861 auto const slotPrice =
IOUAmount{5'200};
2862 ammTokens -= slotPrice;
2864 if (!features[fixAMMv1_3])
2874 for (
int i = 0; i < 10; ++i)
2884 if (!features[fixAMMv1_1])
2888 STAmount(
USD, UINT64_C(29'499'00572620545), -11));
2895 XRP(13'000),
STAmount(
USD, UINT64_C(13'002'98282151419), -11), ammTokens));
2901 STAmount(
USD, UINT64_C(29'499'00572620544), -11));
2907 if (!features[fixAMMv1_3])
2924 for (
int i = 0; i < 10; ++i)
2926 auto const tokens = ammAlice.
deposit(dan,
USD(100));
2932 if (!features[fixAMMv1_1])
2938 XRP(13'000),
STAmount{
USD, UINT64_C(13'012'92609877019), -11}, ammTokens));
2943 XRP(13'000),
STAmount{
USD, UINT64_C(13'112'92609877019), -11}, ammTokens));
2950 STAmount{USD, UINT64_C(13'012'92609877019), -11},
2955 if (!features[fixAMMv1_3])
2959 STAmount(
USD, UINT64_C(19'490'05672274399), -11));
2965 STAmount(
USD, UINT64_C(19'490'05672274398), -11));
2968 if (!features[fixAMMv1_3])
2979 STAmount{USD, UINT64_C(13'012'92609877024), -11},
2985 if (!features[fixAMMv1_3])
2996 STAmount{USD, UINT64_C(13'112'92609877024), -11},
3003 if (!features[fixAMMv1_3])
3007 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3014 STAmount{USD, UINT64_C(13'012'92609877024), -11},
3024 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3028 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3031 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3035 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3042 STAmount{USD, UINT64_C(13'114'03663044937), -11},
3049 if (!features[fixAMMv1_1])
3053 STAmount(
USD, UINT64_C(29'399'00572620545), -11));
3055 else if (!features[fixAMMv1_3])
3059 STAmount(
USD, UINT64_C(29'399'00572620544), -11));
3062 for (
int i = 0; i < 10; ++i)
3069 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3073 STAmount(
USD, UINT64_C(29'389'06197177128), -11));
3076 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3079 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3083 STAmount(
USD, UINT64_C(29'389'06197177124), -11));
3086 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3093 STAmount(
USD, UINT64_C(29'389'06197177129), -11));
3096 STAmount{USD, UINT64_C(13'123'98038488352), -11},
3104 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3111 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3133 [&](AMM& ammAlice, Env& env) {
3135 auto const cleanup340 = features[fixCleanup3_4_0];
3136 auto const expectedPrice = cleanup340 ? minBidPrice : IOUAmount{0};
3137 auto const expectedTokens = cleanup340
3138 ? IOUAmount{Number{ammAlice.tokens()} - Number{minBidPrice}}
3139 : ammAlice.tokens();
3141 env.close(
seconds(kTotalTimeSlotSecs + 1));
3143 env(ammAlice.bid({.account = alice_}));
3144 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, expectedPrice));
3145 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'000), USD(10'000), expectedTokens));
3147 ammAlice.vote(alice_, 1'000);
3148 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, expectedPrice));
3157 [&](AMM& ammAlice, Env& env) {
3159 auto const tiny = Number{STAmount::kMinValue, STAmount::kMinOffset};
3160 auto const cleanup340 = features[fixCleanup3_4_0];
3162 auto const firstPrice = cleanup340 ? minBidPrice : IOUAmount{tiny};
3163 env(ammAlice.bid({.account = alice_, .bidMin = IOUAmount{tiny}}));
3164 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, firstPrice));
3165 BEAST_EXPECT(ammAlice.expectBalances(
3168 cleanup340 ? IOUAmount{Number{ammAlice.tokens()} - Number{minBidPrice}}
3169 : ammAlice.tokens()));
3173 .bidMin = IOUAmount{STAmount::kMinValue, STAmount::kMinOffset},
3176 BEAST_EXPECT(ammAlice.expectAuctionSlot(
3177 0, 0, IOUAmount{Number{firstPrice} * Number{105, -2}}));
3178 BEAST_EXPECT(ammAlice.expectBalances(
3182 ? IOUAmount{Number{ammAlice.tokens()} - Number{minBidPrice} * Number{11, -1}}
3183 : ammAlice.tokens()));
3192 [&](AMM& ammAlice, Env& env) {
3195 .bidMin = IOUAmount{100},
3196 .authAccounts = {carol_},
3198 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol_}));
3199 env(ammAlice.bid({.account = alice_, .bidMin = IOUAmount{100}}));
3200 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3203 fund(env, {bob, dan},
XRP(1'000));
3206 .bidMin = IOUAmount{100},
3207 .authAccounts = {bob, dan},
3209 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3218 Env env(*
this, features);
3219 fund(env, gw_, {alice_, bob_},
XRP(2'000), {USD(2'000)});
3220 AMM
amm(env, gw_,
XRP(1'000), USD(1'010),
false, 1'000);
3221 auto const lpIssue =
amm.lptIssue();
3222 env.trust(STAmount{lpIssue, 500}, alice_);
3223 env.trust(STAmount{lpIssue, 50}, bob_);
3224 env(
pay(gw_, alice_, STAmount{lpIssue, 500}));
3225 env(
pay(gw_, bob_, STAmount{lpIssue, 50}));
3227 env(
amm.bid({.account = alice_, .bidMin = 500}));
3228 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3229 BEAST_EXPECT(
expectHolding(env, alice_, STAmount{lpIssue, 0}));
3232 env(
pay(alice_, bob_, USD(10)), Path(~USD), Sendmax(
XRP(11)));
3233 BEAST_EXPECT(
amm.expectBalances(
3234 XRPAmount{1'010'010'011}, USD(1'000), IOUAmount{1'004'487'562112089, -9}));
3236 env(
pay(bob_, alice_,
XRP(10)), Path(~XRP), Sendmax(USD(11)));
3237 if (!features[fixAMMv1_1])
3239 BEAST_EXPECT(
amm.expectBalances(
3240 XRPAmount{1'000'010'011},
3241 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3242 IOUAmount{1'004'487'562112089, -9}));
3246 BEAST_EXPECT(
amm.expectBalances(
3247 XRPAmount{1'000'010'011},
3248 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3249 IOUAmount{1'004'487'562112089, -9}));
3255 Env env(*
this, features);
3256 auto const baseFee = env.current()->fees().base;
3258 fund(env, gw_, {alice_, bob_},
XRP(2'000), {USD(2'000)});
3259 AMM
amm(env, gw_,
XRP(1'000), USD(1'010),
false, 1'000);
3260 json::Value const tx =
amm.bid({.account = alice_, .bidMin = 500});
3263 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3264 env.app().config().features.erase(featureAMM);
3265 PreflightContext
const pfCtx(
3266 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3267 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3268 BEAST_EXPECT(pf == temDISABLED);
3269 env.app().config().features.insert(featureAMM);
3273 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3274 jtx.jv[
"TxnSignature"] =
"deadbeef";
3275 jtx.stx = env.ust(jtx);
3276 PreflightContext
const pfCtx(
3277 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3278 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3283 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3284 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3285 jtx.jv[
"Asset2"].removeMember(
"issuer");
3286 jtx.stx = env.ust(jtx);
3287 PreflightContext
const pfCtx(
3288 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3289 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3290 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3299 using namespace jtx;
3311 AMM const ammAlice(env, acct,
XRP(10),
USD(10));
3323 AMM const ammAlice(env, acct,
XRP(1'000'000),
USD(100));
3333 auto const baseFee = env.
current()->fees().base;
3344 auto const pk =
carol_.pk();
3345 auto const settleDelay = 100s;
3360 [&](
AMM& ammAlice,
Env& env) {
3397 env(
fset(
gw_, asfGlobalFreeze));
3401 Txflags(tfPartialPayment | tfNoRippleDirect),
3406 Txflags(tfPartialPayment | tfNoRippleDirect),
3418 Txflags(tfPartialPayment | tfNoRippleDirect),
3423 Txflags(tfPartialPayment | tfNoRippleDirect),
3436 Txflags(tfNoRippleDirect | tfPartialPayment),
3445 using namespace jtx;
3450 [&](
AMM& ammAlice,
Env& env) {
3465 {{
XRP(10'000),
USD(10'100)}},
3472 [&](
AMM& ammAlice,
Env& env) {
3484 {{
XRP(10'000),
USD(10'100)}},
3492 [&](
AMM& ammAlice,
Env& env) {
3504 {{
XRP(10'000),
USD(10'100)}},
3511 [&](
AMM& ammAlice,
Env& env) {
3519 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
3534 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
3538 {{
XRP(10'000),
USD(10'010)}},
3545 [&](
AMM& ammAlice,
Env& env) {
3556 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
3565 {{
XRP(10'000),
USD(10'010)}},
3572 [&](
AMM& ammAlice,
Env& env) {
3581 {{
XRP(10'000),
USD(10'000)}},
3591 Env env(*
this, features);
3605 BEAST_EXPECT(ammEurXrp.expectBalances(
3608 ammEurXrp.tokens()));
3609 if (!features[fixAMMv1_1])
3611 BEAST_EXPECT(ammUsdEur.expectBalances(
3614 ammUsdEur.tokens()));
3617 Amounts const expectedAmounts = env.
closed()->rules().enabled(fixReducedOffersV2)
3626 BEAST_EXPECT(ammUsdEur.expectBalances(
3629 ammUsdEur.tokens()));
3632 Amounts const expectedAmounts = env.
closed()->rules().enabled(fixReducedOffersV2)
3652 [&](
AMM& ammAlice,
Env& env) {
3678 STAmount(
USD, UINT64_C(49'98750312422), -11)}}}));
3697 [&](
AMM& ammAlice,
Env& env) {
3704 if (!features[fixAMMv1_1])
3729 {{
XRP(10'000),
USD(10'100)}},
3738 Env env(*
this, features);
3753 [&](
AMM& ammAlice,
Env& env) {
3766 {{
XRP(10'000),
USD(10'100)}},
3774 [&](
AMM& ammAlice,
Env& env) {
3791 {{
GBP(1'000),
EUR(1'100)}},
3802 if (!features[fixAMMv1_1])
3811 BEAST_EXPECT(
amm.expectBalances(
XRP(1'000),
USD(500),
amm.tokens()));
3816 BEAST_EXPECT(
amm.expectBalances(
3827 STAmount(
USD, UINT64_C(29'949'94999999494), -11));
3830 {{
XRP(1'000),
USD(500)}},
3840 if (!features[fixAMMv1_1])
3842 BEAST_EXPECT(
amm.expectBalances(
3848 BEAST_EXPECT(
amm.expectBalances(
3853 {{
XRP(1'000),
USD(500)}},
3859 [&](
AMM& ammAlice,
Env& env) {
3875 if (!features[fixAMMv1_1])
3884 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
3885 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
3894 STAmount{
GBP, UINT64_C(50'684828792831), -12}}}));
3919 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
3920 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
3929 STAmount{
GBP, UINT64_C(27'06583722134028), -14}}}));
3949 {{
GBP(1'000),
EUR(1'100)}},
3962 [&](
AMM& ammAlice,
Env& env) {
3975 {{
GBP(1'000),
EUR(1'100)}},
3992 [&](
AMM& ammAlice,
Env& env) {
3995 auto const can =
gw_[
"CAN"];
3996 fund(env,
gw_, {dan}, {can(200),
GBP(200)}, Fund::Acct);
3998 fund(env,
gw_, {
bob_}, {can(195.3125)}, Fund::Acct);
4002 env(
offer(dan, can(200),
GBP(200)));
4016 {{
GBP(10'000),
EUR(10'125)}},
4023 [&](
AMM& ammAlice,
Env& env) {
4052 Env env(*
this, features);
4053 auto const eth =
gw_[
"ETH"];
4059 {
EUR(50'000),
BTC(50'000), eth(50'000),
USD(50'000)});
4065 AMM const xrpEth(env,
alice_,
XRP(10'000), eth(10'100));
4066 AMM const ethEur(env,
alice_, eth(10'900),
EUR(11'000));
4073 if (!features[fixAMMv1_1])
4079 STAmount{eth, UINT64_C(10'073'65779244494), -11},
4082 STAmount{eth, UINT64_C(10'926'34220755506), -11},
4083 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4086 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4087 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4100 STAmount{eth, UINT64_C(10'073'65779244461), -11},
4103 STAmount{eth, UINT64_C(10'926'34220755539), -11},
4104 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4107 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4108 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4124 BEAST_EXPECT(xrpEur.expectBalances(
XRP(10'100),
EUR(10'000), xrpEur.tokens()));
4133 Env env(*
this, features);
4134 auto const eth =
gw_[
"ETH"];
4140 {
EUR(50'000),
BTC(50'000), eth(50'000),
USD(50'000)});
4145 AMM const xrpEth(env,
alice_,
XRP(10'000), eth(10'100));
4146 AMM const ethEur(env,
alice_, eth(10'900),
EUR(11'000));
4151 if (!features[fixAMMv1_1])
4155 BEAST_EXPECT(xrpEur.expectBalances(
4160 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4161 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4164 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
USD(9'900), btcUsd.
tokens()));
4167 STAmount{eth, UINT64_C(10'017'41072778012), -11},
4170 STAmount{eth, UINT64_C(10'982'58927221988), -11},
4171 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4176 BEAST_EXPECT(xrpEur.expectBalances(
4181 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4182 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4185 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
USD(9'900), btcUsd.
tokens()));
4188 STAmount{eth, UINT64_C(10'017'41072777996), -11},
4191 STAmount{eth, UINT64_C(10'982'58927222004), -11},
4192 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4201 [&](
AMM& ammAlice,
Env& env) {
4205 for (
int i = 0; i < 30; ++i)
4213 Txflags(tfPartialPayment | tfNoRippleDirect));
4214 if (!features[fixAMMv1_1])
4241 [&](
AMM& ammAlice,
Env& env) {
4245 for (
int i = 0; i < 29; ++i)
4253 Txflags(tfPartialPayment | tfNoRippleDirect));
4256 if (!features[fixAMMv1_1])
4280 Env env(*
this, features);
4285 if (!features[fixAMMv1_1])
4289 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4301 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4314 [&](
AMM& ammAlice,
Env& env) {
4321 Txflags(tfNoRippleDirect | tfPartialPayment),
4334 using namespace jtx;
4338 auto const baseFee = env.
current()->fees().base.drops();
4339 auto const token1 = ammAlice.
lptIssue();
4360 env(ammAlice.
bid({.account = carol_, .bidMin = 100}));
4385 ammAlice1.deposit(
carol_, 1'000'000);
4386 auto const token1 = ammAlice.
lptIssue();
4387 auto const token2 = ammAlice1.lptIssue();
4406 auto const token1 = ammAlice.
lptIssue();
4438 using namespace jtx;
4459 using namespace jtx;
4462 auto const info = env.
rpc(
4466 auto const flags = info[jss::result][jss::account_data][jss::Flags].
asUInt();
4467 BEAST_EXPECT(flags == (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
4475 using namespace jtx;
4491 auto const tsta = a[
"TST"];
4492 auto const tstb = b[
"TST"];
4501 env.
trust(tsta(10'000), c);
4502 env.
trust(tstb(10'000), c);
4503 env(
pay(a, c, tsta(10'000)));
4504 env(
pay(b, c, tstb(10'000)));
4505 AMM const amm(env, c, tsta(5'000), tstb(5'000));
4506 auto const ammIss =
Issue(tsta.currency,
amm.ammAccount());
4516 Txflags(tfPartialPayment | tfNoRippleDirect),
4524 testcase(
"AMMAndCLOB, offer quality change");
4525 using namespace jtx;
4526 auto const gw =
Account(
"gw");
4527 auto const tst = gw[
"TST"];
4528 auto const lP1 =
Account(
"LP1");
4529 auto const lP2 =
Account(
"LP2");
4531 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4532 Env env(*
this, features);
4533 env.
fund(
XRP(30'000'000'000), gw);
4534 env(
offer(gw,
XRP(11'500'000'000), tst(1'000'000'000)));
4555 [&](
Env& env) {
AMM const amm(env, lP1, tst(25),
XRP(250)); },
4557 lp2TSTBalance =
getAccountLines(env, lP2, tst)[
"lines"][0u][
"balance"].asString();
4559 lp2TakerGets =
offer[
"taker_gets"].asString();
4560 lp2TakerPays =
offer[
"taker_pays"][
"value"].asString();
4565 if (!features[fixAMMv1_1])
4570 STAmount{tst, UINT64_C(1'68737984885388), -14}),
4578 STAmount{tst, UINT64_C(1'68737976189735), -14}),
4587 BEAST_EXPECT(lp2TakerGets ==
offer[
"taker_gets"].asString());
4588 BEAST_EXPECT(lp2TakerPays ==
offer[
"taker_pays"][
"value"].asString());
4596 using namespace jtx;
4600 [&](
AMM& ammAlice,
Env& env) {
4622 {{
USD(1'000),
EUR(1'000)}},
4630 [&](
AMM& ammAlice,
Env& env) {
4641 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4642 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4652 [&](
AMM& ammAlice,
Env& env) {
4654 auto const tokensFee =
4663 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4664 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4673 [&](
AMM& ammAlice,
Env& env) {
4687 {{
USD(1'000),
EUR(1'000)}},
4694 [&](
AMM& ammAlice,
Env& env) {
4696 auto const tokensFee =
4699 auto const balanceAfterWithdraw = [&]() {
4700 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4702 return STAmount(
USD, UINT64_C(30'443'43891402715), -11);
4704 if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4706 return STAmount(
USD, UINT64_C(30'443'43891402714), -11);
4709 return STAmount(
USD, UINT64_C(30'443'43891402713), -11);
4713 auto const deposit = balanceAfterWithdraw -
USD(29'000);
4719 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4723 STAmount(
USD, UINT64_C(30'443'43891402717), -11));
4725 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4729 STAmount(
USD, UINT64_C(30'443'43891402716), -11));
4735 STAmount(
USD, UINT64_C(30'443'43891402713), -11));
4739 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4741 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4743 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4745 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4749 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779911, -8));
4751 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4760 [&](
AMM& ammAlice,
Env& env) {
4796 {{
USD(1'000),
EUR(1'010)}},
4803 [&](
AMM& ammAlice,
Env& env) {
4829 STAmount{
USD, UINT64_C(5'025125628140703), -15}}}}));
4830 if (!features[fixAMMv1_1])
4833 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4834 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4840 STAmount{USD, UINT64_C(1'004'97487437186), -11},
4841 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4845 {{
USD(1'000),
EUR(1'010)}},
4855 Env env(*
this, features);
4862 if (!features[fixAMMv1_1])
4882 Env env(*
this, features);
4890 if (!features[fixAMMv1_1])
4915 Env env(*
this, features);
4920 AMM const ammAlice(env,
alice_,
USD(1'005),
EUR(1'000),
false, 1'000);
4933 Env env(*
this, features);
4938 AMM const ammAlice(env,
alice_,
USD(1'005),
EUR(1'000),
false, 1'000);
4952 testcase(
"Adjusted Deposit/Withdraw Tokens");
4954 using namespace jtx;
4958 [&](
AMM& ammAlice,
Env& env) {
4966 Account const natalie(
"natalie");
4970 {bob, ed, paul, dan, chris, simon, ben, natalie},
4973 for (
int i = 0; i < 10; ++i)
4997 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5004 else if (features[fixAMMv1_3])
5020 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5025 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5035 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5038 env, natalie,
STAmount{
USD, UINT64_C(1'500'000'000000002), -9}));
5040 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5043 env, natalie,
STAmount{
USD, UINT64_C(1'500'000'000000005), -9}));
5051 if (!features[fixAMMv1_1])
5056 else if (features[fixAMMv1_3])
5078 [&](
AMM& ammAlice,
Env& env) {
5086 Account const natalie(
"natalie");
5090 {bob, ed, paul, dan, chris, simon, ben, natalie},
5094 for (
int i = 0; i < 10; ++i)
5115 auto const baseFee = env.
current()->fees().base.drops();
5116 if (!features[fixAMMv1_3])
5124 auto const xrpBalance = (
XRP(2'000'000) -
txFee(env, 20)).getText();
5149 auto const xrpBalance =
XRP(2'000'000) -
txFee(env, 20) -
drops(10);
5150 auto const xrpBalanceText = xrpBalance.getText();
5177 using namespace jtx;
5200 BEAST_EXPECT(
amm.ammExists());
5210 std::nullopt, 100, std::nullopt, std::nullopt, std::nullopt,
Ter(
tecAMM_EMPTY));
5227 BEAST_EXPECT(
amm.expectBalances(
XRP(10'000),
USD(10'000),
amm.tokens()));
5228 BEAST_EXPECT(
amm.expectTradingFee(1'000));
5229 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5234 BEAST_EXPECT(!
amm.ammExists());
5257 BEAST_EXPECT(
amm.ammExists());
5261 BEAST_EXPECT(
amm.ammExists());
5264 BEAST_EXPECT(!
amm.ammExists());
5276 using namespace jtx;
5288 using namespace jtx;
5290 amm.setClose(
false);
5291 auto const info = env.
rpc(
5298 info[jss::result][jss::account_data][jss::AMMID].asString() ==
5311 for (
auto const& node : affected)
5313 if (node.isMember(sfModifiedNode.fieldName) &&
5314 node[sfModifiedNode.fieldName][sfLedgerEntryType.fieldName].asString() ==
5316 node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::Account]
5319 found = node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::AMMID]
5324 BEAST_EXPECT(found);
5336 testcase(
"Offer/Strand Selection");
5337 using namespace jtx;
5340 auto const eth = gw1[
"ETH"];
5341 auto const can = gw1[
"CAN"];
5347 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5352 env(
rate(gw1, gw1Rate));
5370 for (
auto i = 0; i < 3; ++i)
5372 Env env(*
this, features);
5373 prep(env, rates.first, rates.second);
5375 if (i == 0 || i == 2)
5381 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5387 BEAST_EXPECT(
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5396 BEAST_EXPECT(q[0] > q[1]);
5398 BEAST_EXPECT(q[0] == q[2]);
5405 for (
auto i = 0; i < 3; ++i)
5407 Env env(*
this, features);
5408 prep(env, rates.first, rates.second);
5410 if (i == 0 || i == 2)
5416 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5422 BEAST_EXPECT(
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5424 if (i == 0 || i == 2)
5443 for (
auto i = 0; i < 3; ++i)
5445 Env env(*
this, features);
5446 prep(env, rates.first, rates.second);
5448 if (i == 0 || i == 2)
5454 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5460 BEAST_EXPECT(!
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5462 if (i == 2 && !features[fixAMMv1_1])
5464 if (rates.first == 1.5)
5466 if (!features[fixAMMv1_1])
5473 STAmount{eth, UINT64_C(378'6327949540823), -13},
5474 STAmount{
USD, UINT64_C(283'9745962155617), -13}}}}));
5483 STAmount{eth, UINT64_C(378'6327949540813), -13},
5484 STAmount{
USD, UINT64_C(283'974596215561), -12}}}}));
5489 if (!features[fixAMMv1_1])
5496 STAmount{eth, UINT64_C(325'299461620749), -12},
5497 STAmount{
USD, UINT64_C(243'9745962155617), -13}}}}));
5506 STAmount{eth, UINT64_C(325'299461620748), -12},
5507 STAmount{
USD, UINT64_C(243'974596215561), -12}}}}));
5513 if (rates.first == 1.5)
5520 STAmount{eth, UINT64_C(378'6327949540812), -13},
5521 STAmount{
USD, UINT64_C(283'9745962155609), -13}}}}));
5530 STAmount{eth, UINT64_C(325'2994616207479), -13},
5531 STAmount{
USD, UINT64_C(243'9745962155609), -13}}}}));
5541 BEAST_EXPECT(q[1] > q[0]);
5543 BEAST_EXPECT(q[2] > q[1]);
5547 for (
auto i = 0; i < 3; ++i)
5549 Env env(*
this, features);
5550 prep(env, rates.first, rates.second);
5552 if (i == 0 || i == 2)
5558 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5564 BEAST_EXPECT(!
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5570 if (rates.first == 1.5)
5572 if (!features[fixAMMv1_1])
5581 STAmount{eth, UINT64_C(64'91106406735152), -14},
5594 STAmount{eth, UINT64_C(335'0889359326475), -13},
5602 if (!features[fixAMMv1_1])
5610 STAmount{eth, UINT64_C(335'0889359326485), -13},
5623 STAmount{eth, UINT64_C(335'0889359326475), -13},
5650 for (
auto i = 0; i < 3; ++i)
5652 Env env(*
this, features);
5653 prep(env, rates.first, rates.second);
5656 if (i == 0 || i == 2)
5658 env(
offer(ed, eth(400), can(400)),
Txflags(tfPassive));
5664 amm.emplace(env, ed, eth(1'000),
USD(1'000));
5674 if (i == 2 && !features[fixAMMv1_1])
5676 if (rates.first == 1.5)
5679 BEAST_EXPECT(
amm->expectBalances(
5680 STAmount{eth, UINT64_C(1'176'66038955758), -11},
5686 BEAST_EXPECT(
amm->expectBalances(
5687 STAmount{eth, UINT64_C(1'179'540094339627), -12},
5688 STAmount{USD, UINT64_C(847'7880529867501), -13},
5695 STAmount{eth, UINT64_C(343'3179205198749), -13},
5696 STAmount{can, UINT64_C(343'3179205198749), -13},
5699 STAmount{can, UINT64_C(362'2119470132499), -13},
5706 if (rates.first == 1.5)
5709 BEAST_EXPECT(
amm->expectBalances(
5710 STAmount{eth, UINT64_C(1'176'660389557593), -12},
5716 BEAST_EXPECT(
amm->expectBalances(
5717 STAmount{eth, UINT64_C(1'179'54009433964), -11},
5718 STAmount{USD, UINT64_C(847'7880529867501), -13},
5725 STAmount{eth, UINT64_C(343'3179205198749), -13},
5726 STAmount{can, UINT64_C(343'3179205198749), -13},
5729 STAmount{can, UINT64_C(362'2119470132499), -13},
5739 BEAST_EXPECT(q[1] > q[0]);
5740 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5748 testcase(
"Fix Default Inner Object");
5749 using namespace jtx;
5760 Env env(*
this, features);
5762 AMM amm(env,
gw_,
XRP(10),
USD(10), {.tfee = tfee, .close = closeLedger});
5791 testcase(
"Fix changeSpotPriceQuality");
5792 using namespace jtx;
5797 SucceedShouldSucceedResize,
5814 {
"0.001519763260828713",
"1558701",
Quality{5414253689393440221}, 1000, FailShouldSucceed},
5815 {
"0.01099814367603737",
"1892611",
Quality{5482264816516900274}, 1000, FailShouldSucceed},
5816 {
"0.78",
"796599",
Quality{5630392334958379008}, 1000, FailShouldSucceed},
5817 {
"105439.2955578965",
"49398693",
Quality{5910869983721805038}, 400, FailShouldSucceed},
5818 {
"12408293.23445213",
"4340810521",
Quality{5911611095910090752}, 997, FailShouldSucceed},
5819 {
"1892611",
"0.01099814367603737",
Quality{6703103457950430139}, 1000, FailShouldSucceed},
5820 {
"423028.8508101858",
"3392804520",
Quality{5837920340654162816}, 600, FailShouldSucceed},
5821 {
"44565388.41001027",
"73890647",
Quality{6058976634606450001}, 1000, FailShouldSucceed},
5822 {
"66831.68494832662",
"16",
Quality{6346111134641742975}, 0, FailShouldSucceed},
5823 {
"675.9287302203422",
"1242632304",
Quality{5625960929244093294}, 300, FailShouldSucceed},
5824 {
"7047.112186735699",
"1649845866",
Quality{5696855348026306945}, 504, FailShouldSucceed},
5825 {
"840236.4402981238",
"47419053",
Quality{5982561601648018688}, 499, FailShouldSucceed},
5826 {
"992715.618909774",
"189445631733",
Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
5827 {
"504636667521",
"185545883.9506651",
Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
5828 {
"992706.7218636649",
"189447316000",
Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
5829 {
"1.068737911388205",
"127860278877",
Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
5830 {
"17932506.56880419",
"189308.6043676173",
Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
5831 {
"1.066379294658174",
"128042251493",
Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
5832 {
"350131413924",
"1576879.110907892",
Quality{6487411636539049449}, 650,
Fail},
5833 {
"422093460",
"2.731797662057464",
Quality{6702911108534394924}, 1000,
Fail},
5834 {
"76128132223",
"367172.7148422662",
Quality{6487263463413514240}, 548,
Fail},
5835 {
"132701839250",
"280703770.7695443",
Quality{6273750681188885075}, 562,
Fail},
5836 {
"994165.7604612011",
"189551302411",
Quality{5697835592690668727}, 815,
Fail},
5837 {
"45053.33303227917",
"86612695359",
Quality{5625695218943638190}, 500,
Fail},
5838 {
"199649.077043865",
"14017933007",
Quality{5766034667318524880}, 324,
Fail},
5839 {
"27751824831.70903",
"78896950",
Quality{6272538159621630432}, 500,
Fail},
5840 {
"225.3731275781907",
"156431793648",
Quality{5477818047604078924}, 989,
Fail},
5841 {
"199649.077043865",
"14017933007",
Quality{5766036094462806309}, 324,
Fail},
5842 {
"3.590272027140361",
"20677643641",
Quality{5406056147042156356}, 808,
Fail},
5843 {
"1.070884664490231",
"127604712776",
Quality{5268620608623825741}, 293,
Fail},
5844 {
"3272.448829820197",
"6275124076",
Quality{5625710328924117902}, 81,
Fail},
5845 {
"0.009059512633902926",
"7994028",
Quality{5477511954775533172}, 1000,
Fail},
5848 {
"10",
"10.0",
Quality{xrpIouAmounts10100}, 100,
Fail},
5849 {
"10.0",
"10",
Quality{iouXrpAmounts10100}, 100,
Fail},
5850 {
"69864389131",
"287631.4543025075",
Quality{6487623473313516078}, 451, Succeed},
5851 {
"4328342973",
"12453825.99247381",
Quality{6272522264364865181}, 997, Succeed},
5852 {
"32347017",
"7003.93031579449",
Quality{6347261126087916670}, 1000, Succeed},
5853 {
"61697206161",
"36631.4583206413",
Quality{6558965195382476659}, 500, Succeed},
5854 {
"1654524979",
"7028.659825511603",
Quality{6487551345110052981}, 504, Succeed},
5855 {
"88621.22277293179",
"5128418948",
Quality{5766347291552869205}, 380, Succeed},
5856 {
"1892611",
"0.01099814367603737",
Quality{6703102780512015436}, 1000, Succeed},
5857 {
"4542.639373338766",
"24554809",
Quality{5838994982188783710}, 0, Succeed},
5858 {
"5132932546",
"88542.99750172683",
Quality{6419203342950054537}, 380, Succeed},
5859 {
"78929964.1549083",
"1506494795",
Quality{5986890029845558688}, 589, Succeed},
5860 {
"10096561906",
"44727.72453735605",
Quality{6487455290284644551}, 250, Succeed},
5861 {
"5092.219565514988",
"8768257694",
Quality{5626349534958379008}, 503, Succeed},
5862 {
"1819778294",
"8305.084302902864",
Quality{6487429398998540860}, 415, Succeed},
5863 {
"6970462.633911943",
"57359281",
Quality{6054087899185946624}, 850, Succeed},
5864 {
"3983448845",
"2347.543644281467",
Quality{6558965195382476659}, 856, Succeed},
5868 {
"771493171",
"1.243473020567508",
Quality{6707566798038544272}, 100, SucceedShouldFail},
5872 boost::regex
const rx(
"^\\d+$");
5873 boost::smatch match;
5877 auto rules = env.
current()->rules();
5881 for (
auto const& t :
tests)
5888 auto const& quality = std::get<Quality>(t);
5889 auto const tfee = std::get<std::uint16_t>(t);
5890 auto const status = std::get<Status>(t);
5891 auto const poolInIsXRP = boost::regex_search(std::get<0>(t), match, rx);
5892 auto const poolOutIsXRP = boost::regex_search(std::get<1>(t), match, rx);
5893 assert(!(poolInIsXRP && poolOutIsXRP));
5894 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
5895 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
5902 if (status == SucceedShouldSucceedResize)
5904 if (!features[fixAMMv1_1])
5906 BEAST_EXPECT(
Quality{*amounts} < quality);
5910 BEAST_EXPECT(
Quality{*amounts} >= quality);
5913 else if (status == Succeed)
5915 if (!features[fixAMMv1_1])
5918 Quality{*amounts} >= quality ||
5923 BEAST_EXPECT(
Quality{*amounts} >= quality);
5926 else if (status == FailShouldSucceed)
5928 BEAST_EXPECT(features[fixAMMv1_1] &&
Quality{*amounts} >= quality);
5930 else if (status == SucceedShouldFail)
5933 !features[fixAMMv1_1] &&
Quality{*amounts} < quality &&
5945 auto tinyOffer = [&]() {
5960 auto const takerPays =
5965 BEAST_EXPECT(
Quality(tinyOffer) < quality);
5967 else if (status == FailShouldSucceed)
5969 BEAST_EXPECT(!features[fixAMMv1_1]);
5971 else if (status == SucceedShouldFail)
5973 BEAST_EXPECT(features[fixAMMv1_1]);
5979 BEAST_EXPECT(!
strcmp(e.
what(),
"changeSpotPriceQuality failed"));
5980 BEAST_EXPECT(!features[fixAMMv1_1] && status == FailShouldSucceed);
5988 BEAST_EXPECT(!res.has_value());
5995 using namespace jtx;
5999 .flags = tfSingleAsset,
6007 .flags = tfOneAssetLPToken,
6015 .flags = tfLimitLPToken,
6023 .asset1Out =
XRP(100),
6024 .asset2Out =
XRP(100),
6032 .asset1Out =
XRP(100),
6033 .asset2Out =
BAD(100),
6041 jv[jss::TransactionType] = jss::AMMWithdraw;
6042 jv[jss::Flags] = tfLimitLPToken;
6043 jv[jss::Account] =
alice_.human();
6045 XRP(100).value().setJson(jv[jss::Amount]);
6046 USD(100).value().setJson(jv[jss::EPrice]);
6055 using namespace jtx;
6061 Account const gatehub{
"gatehub"};
6062 Account const bitstamp{
"bitstamp"};
6063 Account const trader{
"trader"};
6064 auto const usdGH = gatehub[
"USD"];
6065 auto const btcGH = gatehub[
"BTC"];
6066 auto const usdBIT = bitstamp[
"USD"];
6070 char const* testCase;
6071 double const poolUsdBIT;
6072 double const poolUsdGH;
6085 double const offer1BtcGH = 0.1;
6086 double const offer2BtcGH = 0.1;
6087 double const offer2UsdGH = 1;
6088 double const rateBIT = 0.0;
6089 double const rateGH = 0.0;
6094 for (
auto const& input : {
6096 .testCase =
"Test Fix Overflow Offer",
6099 .sendMaxUsdBIT{usdBIT(50)},
6100 .sendUsdGH{usdGH, Uint64T(272'455089820359), -12},
6103 .failUsdBIT{usdBIT, Uint64T(46'47826086956522), -14},
6104 .failUsdBITr{usdBIT, Uint64T(46'47826086956521), -14},
6105 .goodUsdGH{usdGH, Uint64T(96'7543114220382), -13},
6106 .goodUsdGHr{usdGH, Uint64T(96'7543114222965), -13},
6107 .goodUsdBIT{usdBIT, Uint64T(8'464739069120721), -15},
6108 .goodUsdBITr{usdBIT, Uint64T(8'464739069098152), -15},
6109 .lpTokenBalance = {28'61817604250837, -14},
6110 .lpTokenBalanceAlt =
IOUAmount{28'61817604250836, -14},
6118 .testCase =
"Overflow test {1, 100, 0.111}",
6121 .sendMaxUsdBIT{usdBIT(0.111)},
6122 .sendUsdGH{usdGH, 100},
6125 .failUsdBIT{usdBIT, Uint64T(1'111), -3},
6126 .failUsdBITr{usdBIT, Uint64T(1'111), -3},
6127 .goodUsdGH{usdGH, Uint64T(90'04347888284115), -14},
6128 .goodUsdGHr{usdGH, Uint64T(90'04347888284201), -14},
6129 .goodUsdBIT{usdBIT, Uint64T(1'111), -3},
6130 .goodUsdBITr{usdBIT, Uint64T(1'111), -3},
6131 .lpTokenBalance{10, 0},
6132 .offer1BtcGH = 1e-5,
6134 .offer2UsdGH = 1e-5,
6139 .testCase =
"Overflow test {1, 100, 1.00}",
6142 .sendMaxUsdBIT{usdBIT(1.00)},
6143 .sendUsdGH{usdGH, 100},
6146 .failUsdBIT{usdBIT, Uint64T(2), 0},
6147 .failUsdBITr{usdBIT, Uint64T(2), 0},
6148 .goodUsdGH{usdGH, Uint64T(52'94379354424079), -14},
6149 .goodUsdGHr{usdGH, Uint64T(52'94379354424135), -14},
6150 .goodUsdBIT{usdBIT, Uint64T(2), 0},
6151 .goodUsdBITr{usdBIT, Uint64T(2), 0},
6152 .lpTokenBalance{10, 0},
6153 .offer1BtcGH = 1e-5,
6155 .offer2UsdGH = 1e-5,
6160 .testCase =
"Overflow test {1, 100, 4.6432}",
6163 .sendMaxUsdBIT{usdBIT(4.6432)},
6164 .sendUsdGH{usdGH, 100},
6167 .failUsdBIT{usdBIT, Uint64T(5'6432), -4},
6168 .failUsdBITr{usdBIT, Uint64T(5'6432), -4},
6169 .goodUsdGH{usdGH, Uint64T(35'44113971506987), -14},
6170 .goodUsdGHr{usdGH, Uint64T(35'44113971506987), -14},
6171 .goodUsdBIT{usdBIT, Uint64T(2'821579689703915), -15},
6172 .goodUsdBITr{usdBIT, Uint64T(2'821579689703954), -15},
6173 .lpTokenBalance{10, 0},
6174 .offer1BtcGH = 1e-5,
6176 .offer2UsdGH = 1e-5,
6181 .testCase =
"Overflow test {1, 100, 10}",
6184 .sendMaxUsdBIT{usdBIT(10)},
6185 .sendUsdGH{usdGH, 100},
6188 .failUsdBIT{usdBIT, Uint64T(11), 0},
6189 .failUsdBITr{usdBIT, Uint64T(11), 0},
6190 .goodUsdGH{usdGH, Uint64T(35'44113971506987), -14},
6191 .goodUsdGHr{usdGH, Uint64T(35'44113971506987), -14},
6192 .goodUsdBIT{usdBIT, Uint64T(2'821579689703915), -15},
6193 .goodUsdBITr{usdBIT, Uint64T(2'821579689703954), -15},
6194 .lpTokenBalance{10, 0},
6195 .offer1BtcGH = 1e-5,
6197 .offer2UsdGH = 1e-5,
6202 .testCase =
"Overflow test {50, 100, 5.55}",
6205 .sendMaxUsdBIT{usdBIT(5.55)},
6206 .sendUsdGH{usdGH, 100},
6209 .failUsdBIT{usdBIT, Uint64T(55'55), -2},
6210 .failUsdBITr{usdBIT, Uint64T(55'55), -2},
6211 .goodUsdGH{usdGH, Uint64T(90'04347888284113), -14},
6212 .goodUsdGHr{usdGH, Uint64T(90'0434788828413), -13},
6213 .goodUsdBIT{usdBIT, Uint64T(55'55), -2},
6214 .goodUsdBITr{usdBIT, Uint64T(55'55), -2},
6215 .lpTokenBalance{Uint64T(70'71067811865475), -14},
6216 .offer1BtcGH = 1e-5,
6218 .offer2UsdGH = 1e-5,
6223 .testCase =
"Overflow test {50, 100, 50.00}",
6226 .sendMaxUsdBIT{usdBIT(50.00)},
6227 .sendUsdGH{usdGH, 100},
6228 .failUsdGH{usdGH, Uint64T(52'94379354424081), -14},
6229 .failUsdGHr{usdGH, Uint64T(52'94379354424092), -14},
6230 .failUsdBIT{usdBIT, Uint64T(100), 0},
6231 .failUsdBITr{usdBIT, Uint64T(100), 0},
6232 .goodUsdGH{usdGH, Uint64T(52'94379354424081), -14},
6233 .goodUsdGHr{usdGH, Uint64T(52'94379354424092), -14},
6234 .goodUsdBIT{usdBIT, Uint64T(100), 0},
6235 .goodUsdBITr{usdBIT, Uint64T(100), 0},
6236 .lpTokenBalance{Uint64T(70'71067811865475), -14},
6237 .offer1BtcGH = 1e-5,
6239 .offer2UsdGH = 1e-5,
6244 .testCase =
"Overflow test {50, 100, 232.16}",
6247 .sendMaxUsdBIT{usdBIT(232.16)},
6248 .sendUsdGH{usdGH, 100},
6251 .failUsdBIT{usdBIT, Uint64T(282'16), -2},
6252 .failUsdBITr{usdBIT, Uint64T(282'16), -2},
6253 .goodUsdGH{usdGH, Uint64T(35'44113971506987), -14},
6254 .goodUsdGHr{usdGH, Uint64T(35'44113971506987), -14},
6255 .goodUsdBIT{usdBIT, Uint64T(141'0789844851958), -13},
6256 .goodUsdBITr{usdBIT, Uint64T(141'0789844851962), -13},
6257 .lpTokenBalance{70'71067811865475, -14},
6258 .offer1BtcGH = 1e-5,
6260 .offer2UsdGH = 1e-5,
6265 .testCase =
"Overflow test {50, 100, 500}",
6268 .sendMaxUsdBIT{usdBIT(500)},
6269 .sendUsdGH{usdGH, 100},
6272 .failUsdBIT{usdBIT, Uint64T(550), 0},
6273 .failUsdBITr{usdBIT, Uint64T(550), 0},
6274 .goodUsdGH{usdGH, Uint64T(35'44113971506987), -14},
6275 .goodUsdGHr{usdGH, Uint64T(35'44113971506987), -14},
6276 .goodUsdBIT{usdBIT, Uint64T(141'0789844851958), -13},
6277 .goodUsdBITr{usdBIT, Uint64T(141'0789844851962), -13},
6278 .lpTokenBalance{70'71067811865475, -14},
6279 .offer1BtcGH = 1e-5,
6281 .offer2UsdGH = 1e-5,
6288 for (
auto const& features : {all - fixAMMv1_1 - fixAMMv1_3, all})
6292 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6295 if (input.rateGH != 0.0)
6296 env(
rate(gatehub, input.rateGH));
6297 if (input.rateBIT != 0.0)
6298 env(
rate(bitstamp, input.rateBIT));
6300 env(
trust(trader, usdGH(10'000'000)));
6301 env(
trust(trader, usdBIT(10'000'000)));
6302 env(
trust(trader, btcGH(10'000'000)));
6305 env(
pay(gatehub, trader, usdGH(100'000)));
6306 env(
pay(gatehub, trader, btcGH(100'000)));
6307 env(
pay(bitstamp, trader, usdBIT(100'000)));
6310 AMM const amm{env, trader, usdGH(input.poolUsdGH), usdBIT(input.poolUsdBIT)};
6313 IOUAmount const preSwapLPTokenBalance =
amm.getLPTokensBalance();
6315 env(
offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6316 env(
offer(trader, btcGH(input.offer2BtcGH), usdGH(input.offer2UsdGH)));
6319 env(
pay(trader, trader, input.sendUsdGH),
6321 Path(~btcGH, ~usdGH),
6326 auto const failUsdGH = features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6327 auto const failUsdBIT = features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6328 auto const goodUsdGH = features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6329 auto const goodUsdBIT = features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6330 auto const lpTokenBalance = [&] {
6331 if (not env.
enabled(fixAMMv1_3))
6332 return input.lpTokenBalance;
6334 return input.lpTokenBalanceAlt.value_or(input.lpTokenBalance);
6338 BEAST_EXPECT(
amm.expectBalances(goodUsdGH, goodUsdBIT, lpTokenBalance));
6342 BEAST_EXPECT(
amm.getLPTokensBalance() == preSwapLPTokenBalance);
6346 Number const sqrtPoolProduct =
root2(goodUsdGH * goodUsdBIT);
6356 BEAST_EXPECT((sqrtPoolProduct +
Number{1, -14} >= input.lpTokenBalance));
6366 using namespace jtx;
6368 STAmount const xrpPool{
XRP, UINT64_C(51600'000981)};
6369 STAmount const iouPool{
USD, UINT64_C(803040'9987141784), -10};
6371 STAmount const xrpBob{
XRP, UINT64_C(1092'878933)};
6372 STAmount const iouBob{
USD, UINT64_C(3'988035892323031), -28};
6377 auto [xrpBegin, iouBegin, lptBegin] =
amm.balances(
XRP,
USD);
6389 BEAST_EXPECT(
amm.expectBalances(xrpBegin, iouBegin,
amm.tokens()));
6391 {{xrpPool, iouPool}},
6400 testcase(
"AMM Offer Blocked By LOB");
6401 using namespace jtx;
6407 Env env(*
this, features);
6421 if (!features[fixAMMv1_1])
6423 BEAST_EXPECT(
amm.expectBalances(
XRP(200'000),
USD(100'000),
amm.tokens()));
6441 Env env(*
this, features);
6462 Env env(*
this, features);
6472 if (!features[fixAMMv1_1])
6474 BEAST_EXPECT(
amm.expectBalances(
XRP(1'000),
USD(500),
amm.tokens()));
6480 BEAST_EXPECT(
amm.expectBalances(
6496 Env env(*
this, features);
6502 BEAST_EXPECT(
amm.expectBalances(
6513 using namespace jtx;
6527 auto const lpToken =
6529 auto const lpTokenBalance =
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6530 BEAST_EXPECT(lpToken ==
"1414.213562373095" && lpTokenBalance ==
"1414.213562373");
6531 if (!features[fixAMMv1_1])
6534 BEAST_EXPECT(
amm.ammExists());
6539 BEAST_EXPECT(!
amm.ammExists());
6545 for (
auto const& lp : {
gw_,
bob_})
6547 Env env(*
this, features);
6548 auto const abc =
gw_[
"ABC"];
6554 {
USD(1'000'000'000), abc(1'000'000'000'000)});
6555 AMM amm(env, lp, abc(2'000'000),
USD(1));
6560 auto const lpToken =
6562 auto const lpTokenBalance =
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6563 BEAST_EXPECT(lpToken ==
"1414.213562373095" && lpTokenBalance ==
"1414.213562373");
6564 if (!features[fixAMMv1_1])
6567 BEAST_EXPECT(
amm.ammExists());
6571 amm.withdrawAll(lp);
6572 BEAST_EXPECT(!
amm.ammExists());
6579 Env env(*
this, features);
6584 BEAST_EXPECT(res && !res.value());
6586 BEAST_EXPECT(res && !res.value());
6590 Env env(*
this, features);
6595 BEAST_EXPECT(res && !res.value());
6597 BEAST_EXPECT(res && !res.value());
6601 Env env(*
this, features);
6603 auto const yan = gw1[
"YAN"];
6605 fund(env, gw1, {
gw_},
XRP(1'000), {yan(1'000)}, Fund::TokenOnly);
6609 BEAST_EXPECT(res && !res.value());
6611 BEAST_EXPECT(res && !res.value());
6618 testcase(
"test clawback from AMM account");
6619 using namespace jtx;
6622 Env env(*
this, features);
6624 env(
fset(
gw_, asfAllowTrustLineClawback));
6630 if (!features[featureAMMClawback])
6634 env(
fclear(
gw_, asfAllowTrustLineClawback));
6659 Issue const usd(
USD.issue().currency,
amm.ammAccount());
6661 env(
claw(
gw_, amount), error);
6668 testcase(
"test AMMDeposit with frozen assets");
6669 using namespace jtx;
6686 Env env(*
this, features);
6687 testAMMDeposit(env, [&](
AMM&
amm) {
6695 Env env(*
this, features);
6696 testAMMDeposit(env, [&](
AMM&
amm) {
6702 if (features[featureAMMClawback] || features[fixCleanup3_3_0])
6707 Env env(*
this, features);
6708 testAMMDeposit(env, [&](
AMM&
amm) {
6718 Env env(*
this, features);
6719 testAMMDeposit(env, [&](
AMM&
amm) {
6729 testcase(
"Fix Reserve Check On Withdrawal");
6730 using namespace jtx;
6734 auto test = [&](
auto&& cb) {
6735 Env env(*
this, features);
6736 auto const startingXrp =
reserve(env, 2) + env.
current()->fees().base * 5;
6755 .account =
alice_, .asset1Out =
EUR(0.1), .asset2Out =
USD(0.1), .err = err});
6758 .account =
alice_, .asset1Out =
USD(0.1), .asset2Out =
EUR(0.1), .err = err});
6774 testcase(
"Fail pseudo-account allocation " + suffix);
6786 for (
int i = 0; i < 256; ++i)
6811 testcase(
"Deposit and Withdraw Rounding V2");
6812 using namespace jtx;
6814 auto const xpm =
gw_[
"XPM"];
6816 STAmount xpmBalance{xpm, UINT64_C(18'610'359'80246901), -8};
6817 STAmount amount{xpm, UINT64_C(6'566'496939465400), -12};
6821 Env env(*
this, features);
6829 STAmount const lptAMMBalance{
amm.lptIssue(), UINT64_C(3'234'987'266'485968), -6};
6830 auto const burn =
IOUAmount{
amm.getLPTokensBalance() - lptAMMBalance};
6832 env(
amm.bid(
BidArg{.account = gw_, .bidMin = burn, .bidMax = burn}));
6843 auto const [amount, amount2, lptAMM] =
amm.balances(
XRP, xpm);
6844 auto const withdraw =
STAmount{xpm, 1, -5};
6846 auto const [amount_, amount2_, lptAMM_] =
amm.balances(
XRP, xpm);
6849 BEAST_EXPECT((amount2 - amount2_) > withdraw);
6853 BEAST_EXPECT((amount2 - amount2_) <= withdraw);
6862 auto const [amount, amount2, lptBalance] =
amm.balances(
GBP,
EUR);
6867 auto const res =
root2(amount * amount2);
6871 BEAST_EXPECT(res < lptBalance);
6875 BEAST_EXPECT(res >= lptBalance);
6883 using namespace jtx;
6895 [&](
AMM& ammAlice,
Env& env) {
6901 {
GBP(100'000),
EUR(100'000)},
6912 {{
GBP(30'000),
EUR(30'000)}},
6920 [&](
AMM& ammAlice,
Env& env) {
6924 STAmount const depositEuro{
EUR, UINT64_C(10'1234567890123456), -16};
6925 STAmount const depositGBP{
GBP, UINT64_C(10'1234567890123456), -16};
6928 DepositArg{.account =
bob_, .asset1In = depositEuro, .asset2In = depositGBP});
6929 invariant(ammAlice, env,
"dep2",
false);
6931 {{
GBP(30'000),
EUR(30'000)}},
6937 for (
auto const& exponent : {1, 2, 3, 4, -3 , -6, -9})
6940 [&](
AMM& ammAlice,
Env& env) {
6946 {
GBP(100'000),
EUR(100'000)},
6955 .account =
bob_, .asset1In = depositEuro, .asset2In = depositGBP});
6956 invariant(ammAlice, env,
"dep3", exponent != -3 && !env.
enabled(fixAMMv1_3));
6958 {{
GBP(10'000),
EUR(30'000)}},
6966 [&](
AMM& ammAlice,
Env& env) {
6972 invariant(ammAlice, env,
"dep4",
false);
6974 {{
GBP(7'000),
EUR(30'000)}},
6980 for (
auto const& tokens :
6991 [&](
AMM& ammAlice,
Env& env) {
6997 {
GBP(100'000),
EUR(1'000'000)},
7004 invariant(ammAlice, env,
"dep5",
false);
7006 {{
GBP(7'000),
EUR(30'000)}},
7015 [&](
AMM& ammAlice,
Env& env) {
7020 invariant(ammAlice, env,
"dep6",
false);
7022 {{
GBP(30'000),
EUR(30'000)}},
7033 using namespace jtx;
7037 [&](
AMM& ammAlice,
Env& env) {
7039 invariant(ammAlice, env,
"with1",
false);
7041 {{
GBP(7'000),
EUR(30'000)}},
7048 [&](
AMM& ammAlice,
Env& env) {
7050 invariant(ammAlice, env,
"with2",
false);
7052 {{
GBP(7'000),
EUR(30'000)}},
7059 [&](
AMM& ammAlice,
Env& env) {
7065 .flags = tfTwoAsset});
7066 invariant(ammAlice, env,
"with3",
false);
7068 {{
GBP(7'000),
EUR(30'000)}},
7079 [&](
AMM& ammAlice,
Env& env) {
7084 .flags = tfSingleAsset});
7085 invariant(ammAlice, env,
"with4",
false);
7087 {{
GBP(7'000),
EUR(30'000)}},
7094 [&](
AMM& ammAlice,
Env& env) {
7104 .flags = tfOneAssetWithdrawAll});
7105 invariant(ammAlice, env,
"with5",
false);
7107 {{
GBP(7'000),
EUR(30'000)}},
7114 [&](
AMM& ammAlice,
Env& env) {
7120 .flags = tfOneAssetLPToken});
7121 invariant(ammAlice, env,
"with6",
false);
7123 {{
GBP(7'000),
EUR(30'000)}},
7130 [&](
AMM& ammAlice,
Env& env) {
7136 .flags = tfLimitLPToken});
7137 invariant(ammAlice, env,
"with7",
true);
7139 {{
GBP(7'000),
EUR(30'000)}},
7148 testcase(
"Test AuthAccounts reset after empty pool reinitialization");
7149 using namespace jtx;
7176 .authAccounts = {bob_, dan},
7179 BEAST_EXPECT(
amm.expectAuctionSlot({bob_.id(), dan.id()}));
7182 amm.withdrawAll(alice_);
7183 amm.withdrawAll(carol_);
7184 BEAST_EXPECT(
amm.ammExists());
7187 BEAST_EXPECT(
amm.getLPTokensBalance() ==
IOUAmount{0});
7188 BEAST_EXPECT(
amm.expectAuctionSlot({bob_.id(), dan.id()}));
7202 BEAST_EXPECT(ammSle && ammSle->isFieldPresent(sfAuctionSlot));
7207 BEAST_EXPECT(
amm.expectAuctionSlot(50, 0,
IOUAmount{0}));
7210 BEAST_EXPECT(slot[sfAccount] == ed.id());
7212 BEAST_EXPECT(ammSle->getFieldU16(sfTradingFee) == 500);
7214 auto const& votes = ammSle->getFieldArray(sfVoteSlots);
7215 BEAST_EXPECT(votes.size() == 1);
7218 BEAST_EXPECT(votes[0].getAccountID(sfAccount) == ed.id());
7219 BEAST_EXPECT(votes[0].getFieldU16(sfTradingFee) == 500);
7223 if (features[fixCleanup3_2_0])
7225 BEAST_EXPECT(!slot.isFieldPresent(sfAuthAccounts));
7230 slot.isFieldPresent(sfAuthAccounts) && !slot.getFieldArray(sfAuthAccounts).empty());
7237 testcase(
"Deposit integral overflow");
7239 using namespace jtx;
7275 .asset1In = asset1In,
7277 .err =
Ter(expected)});
7293 testcase(
"Deposit EPrice integral overflow");
7295 using namespace jtx;
7329 .account =
alice_, .asset1In =
XRP(0), .maxEP = ePrice, .err =
Ter(expected)});
7354 testcase(
"Withdraw integral no overflow");
7356 using namespace jtx;
7375 Env env(*
this, features);
7391 .asset2Out =
XRP(1),
7413 testBid(all - fixCleanup3_4_0);
7415 testBid(all - fixAMMv1_1 - fixAMMv1_3);
void fail(String const &reason, char const *file, int line)
Record a failure.
TestcaseT testcase
Memberspace for declaring test cases.
RAII class to set and restore the current transaction rules.
Floating point representation of amounts with high dynamic range.
A currency issued by an account.
std::chrono::time_point< NetClock > time_point
Sets the new scale and restores the old scale when it leaves scope.
Number is a floating point type that can represent a wide range of values.
static RoundingMode getround()
Represents the logical ratio of output currency to input currency.
Asset const & asset() const
json::Value getJson(JsonOptions) const override
jtx::Account const alice_
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> const &cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::Ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={testableAmendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
jtx::Account const carol_
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
static XRPAmount ammCrtFee(jtx::Env &env)
Convenience class to test AMM functionality.
bool expectTradingFee(std::uint16_t fee) const
json::Value bid(BidArg const &arg)
IOUAmount getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< Ter > const &ter=std::nullopt)
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< Ter > const &ter=std::nullopt)
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< Ter > const &ter=std::nullopt)
json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Asset > const &asset1=std::nullopt, std::optional< Asset > const &asset2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=rpc::kApiInvalidVersion) const
Send amm_info RPC command.
AccountID const & ammAccount() const
void setTokens(json::Value &jv, std::optional< std::pair< Asset, Asset > > const &assets=std::nullopt)
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::Seq > const &seq=std::nullopt, std::optional< std::pair< Asset, Asset > > const &assets=std::nullopt, std::optional< Ter > const &ter=std::nullopt)
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
SLE::const_pointer le(Account const &account) const
Return an account root.
bool enabled(UInt256 feature) const
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
void memoize(Account const &account)
Associate AccountID with account.
beast::Journal const journal
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
NetClock::time_point now()
Returns the current network time.
Sets the SendMax on a JTx.
Set the regular signature on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
@ Object
object value (collection of name/value pairs).
Keylet computation functions.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Deposit preauthorize operations.
json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount)
auto const kCancelTime
Set the "CancelAfter" time tag on a JTx.
auto const kFinishTime
Set the "FinishAfter" time tag on a JTx.
std::array< std::uint8_t, 39 > const kCb1
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 claw(Account const &account, STAmount const &amount, std::optional< Account > const &mptHolder)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
std::vector< STAmount > fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
std::vector< FeatureBitset > amendmentCombinations(std::initializer_list< UInt256 > features, FeatureBitset seed)
Returns all 2^N permutations of a seed FeatureBitset with each subset of the given features excluded.
bool expectHolding(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
XrpT const XRP
Converts to XRP Issue or STAmount.
XRPAmount txFee(Env const &env, std::uint16_t n)
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
FeatureBitset testableAmendments()
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
static AutofillT const kAutofill
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value accountBalance(Env &env, Account const &acct)
json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
json::Value getAccountLines(Env &env, AccountID const &acctId)
json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
constexpr XRPAmount
Convert XRP to drops (integral types).
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
constexpr std::uint32_t kTotalTimeSlotSecs
bool isXRP(AccountID const &c)
Number ammAuctionMinSlotPrice(Number const &lptAMMBalance, std::uint16_t tradingFee)
Minimum auction slot price: LPTokens * TradingFee / kAuctionSlotMinFeeFraction.
std::expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
TAmounts< STAmount, STAmount > Amounts
@ Fail
Should not be retried in this ledger.
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
STAmount amountFromString(Asset const &asset, std::string const &amount)
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
constexpr std::uint16_t kMaxDeletableAmmTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
std::string to_string(BaseUInt< Bits, Tag > const &a)
T toAmount(STAmount const &amt)=delete
Asset getAsset(T const &amt)
TERSubset< CanCvtToNotTEC > NotTEC
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
AccountID pseudoAccountAddress(ReadView const &view, UInt256 const &pseudoOwnerKey)
Generate a pseudo-account address from a pseudo owner key.
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
constexpr std::uint16_t kAuctionSlotTimeIntervals
Dest safeDowncast(Src *s) noexcept
bool isTesSuccess(TER x) noexcept
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
TERSubset< CanCvtToTER > TER
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
@ tecINSUFFICIENT_RESERVE
constexpr std::uint32_t kVoteWeightScaleFactor
constexpr std::uint32_t kAuctionSlotIntervalDuration
Zero allows classes to offer efficient comparisons to zero.
Represents a pair of input and output currencies.
Basic tests of AMM that do not use offers.
void testAMMAndCLOB(FeatureBitset features)
static FeatureBitset testableAmendments()
static std::vector< FeatureBitset > amendmentCombinations(std::initializer_list< UInt256 > features)
void run() override
Runs the suite.
void testTradingFee(FeatureBitset features)
void testWithdrawRounding(FeatureBitset all)
void testWithdrawIntegralNoOverflow()
void testOverflowOffer(FeatureBitset featuresInitial)
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
void testSelection(FeatureBitset features)
void testInvalidDeposit(FeatureBitset features)
void invariant(jtx::AMM &amm, jtx::Env &env, std::string const &msg, bool shouldFail)
void testBid(FeatureBitset features)
void testInstanceCreate()
void testDepositEPriceIntegralOverflow()
NumberMantissaScaleGuard const sg
void testFixChangeSpotPriceQuality(FeatureBitset features)
void testDepositRounding(FeatureBitset all)
void testInvalidFeeVote()
void testInvalidInstance()
void testInvalidWithdraw()
void testStaleAuthAccountsAfterReinit(FeatureBitset features)
void testAMMClawback(FeatureBitset features)
void testDepositIntegralOverflow()
void testLPTokenBalance(FeatureBitset features)
void testAMMDepositWithFrozenAssets(FeatureBitset features)
void testAdjustedTokens(FeatureBitset features)
void testFailedPseudoAccount()
void testBasicPaymentEngine(FeatureBitset features)
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
void testDepositAndWithdrawRounding(FeatureBitset features)
void testFixDefaultInnerObj()
void testInvalidAMMPayment()
Set the sequence number on a JTx.