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>
107 [&](
AMM& ammAlice,
Env&) {
119 [&](
AMM& ammAlice,
Env&) {
130 [&](
AMM& ammAlice,
Env&) {
133 {{
USD(20'000),
BTC(0.5)}});
153 env(
fset(
gw_, asfRequireAuth));
171 env(
fset(
gw_, asfGlobalFreeze));
182 BEAST_EXPECT(
amm.expectTradingFee(1'000));
183 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
209 BEAST_EXPECT(!ammAlice.ammExists());
217 BEAST_EXPECT(!ammAlice.ammExists());
225 BEAST_EXPECT(!ammAlice.ammExists());
239 BEAST_EXPECT(!ammAlice.ammExists());
247 BEAST_EXPECT(!ammAlice.ammExists());
255 BEAST_EXPECT(!ammAlice.ammExists());
274 BEAST_EXPECT(!ammAlice.ammExists());
298 BEAST_EXPECT(!ammAlice.ammExists());
326 env(
fset(
gw_, asfRequireAuth));
339 env(
fset(
gw_, asfGlobalFreeze));
343 for (
auto const& account : {
alice_,
gw_})
346 BEAST_EXPECT(!
amm.ammExists());
368 auto const startingXrp =
XRP(1'000) +
reserve(env, 3) + env.
current()->fees().base * 4;
383 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 5;
420 AMM const ammAMMToken(
426 AMM const ammAMMToken1(
438 auto const token1 = ammAlice.
lptIssue();
439 auto const token2 = ammAlice1.lptIssue();
440 AMM const ammAMMTokens(
460 env(
fclear(gw1, asfDefaultRipple));
463 auto const usD1 = gw1[
"USD"];
491 {tfLPToken, 1'000, std::nullopt,
USD(100), std::nullopt, std::nullopt},
492 {tfLPToken, 1'000,
XRP(100), std::nullopt, std::nullopt, std::nullopt},
493 {tfLPToken, 1'000, std::nullopt, std::nullopt,
STAmount{
USD, 1, -1}, std::nullopt},
500 {tfLPToken, 1'000,
XRP(100), std::nullopt,
STAmount{
USD, 1, -1}, std::nullopt},
501 {tfLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, 1'000},
502 {tfSingleAsset, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
503 {tfSingleAsset, std::nullopt, std::nullopt,
USD(100), std::nullopt, std::nullopt},
510 {tfSingleAsset, std::nullopt,
USD(100), std::nullopt, std::nullopt, 1'000},
511 {tfTwoAsset, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
512 {tfTwoAsset, std::nullopt,
XRP(100),
USD(100),
STAmount{
USD, 1, -1}, std::nullopt},
513 {tfTwoAsset, std::nullopt,
XRP(100), std::nullopt, std::nullopt, std::nullopt},
514 {tfTwoAsset, std::nullopt,
XRP(100),
USD(100), std::nullopt, 1'000},
521 {tfOneAssetLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
522 {tfOneAssetLPToken, std::nullopt,
XRP(100),
USD(100), std::nullopt, std::nullopt},
529 {tfOneAssetLPToken, 1'000,
XRP(100), std::nullopt, std::nullopt, 1'000},
530 {tfLimitLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
531 {tfLimitLPToken, 1'000,
USD(100), std::nullopt, std::nullopt, std::nullopt},
532 {tfLimitLPToken, std::nullopt,
USD(100),
XRP(100), std::nullopt, std::nullopt},
533 {tfLimitLPToken, std::nullopt,
XRP(100), std::nullopt,
STAmount{
USD, 1, -1}, 1'000},
534 {tfTwoAssetIfEmpty, std::nullopt, std::nullopt, std::nullopt, std::nullopt, 1'000},
535 {tfTwoAssetIfEmpty, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
542 {tfTwoAssetIfEmpty | tfLPToken,
548 for (
auto const& it : invalidOptions)
566 jv[jss::Account] =
alice_.human();
567 jv[jss::TransactionType] = jss::AMMDeposit;
581 jv[jss::Account] =
alice_.human();
582 jv[jss::TransactionType] = jss::AMMDeposit;
586 jv[jss::Flags] = tfLPToken;
725 [&](
AMM& ammAlice,
Env& env) {
726 auto const enabledV13 = env.
current()->rules().enabled(fixAMMv1_3);
746 {features, features - fixAMMv1_3});
756 [&](
AMM& ammAlice,
Env& env) {
757 env(
fset(
gw_, asfGlobalFreeze));
758 auto const freezeBlocksAll =
759 features[featureAMMClawback] || features[fixCleanup3_3_0];
760 if (!freezeBlocksAll)
774 for (
auto const& account : {
carol_,
gw_})
784 account, 1'000'000, std::nullopt, std::nullopt,
Ter(
tecFROZEN));
796 [&](
AMM& ammAlice,
Env& env) {
799 auto const freezeBlocksAll =
800 features[featureAMMClawback] || features[fixCleanup3_3_0];
801 if (!freezeBlocksAll)
828 if (!features[fixCleanup3_3_0])
848 [&](
AMM& ammAlice,
Env& env) {
867 {{
USD(20'000),
BTC(0.5)}});
871 Env env(*
this, features);
873 env(
fset(
gw_, asfRequireAuth));
886 if (features[featureAMMClawback] || features[fixCleanup3_3_0])
958 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 4;
992 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 4;
1159 {.pool = {{
USD(1'000'000),
XRP(1'000'000)}}, .features = {features - fixAMMv1_3}});
1199 using namespace jtx;
1203 auto const baseFee = env.
current()->fees().base;
1216 for (
Number const& deltaLPTokens :
1217 {
Number{UINT64_C(100000'0000000009), -10},
Number{UINT64_C(100000'0000000001), -10}})
1222 IOUAmount const newLPTokens{deltaLPTokens};
1234 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1235 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1239 Number const fr = deltaLPTokens / 1e7;
1243 Number const deltaXRP = fr * 1e10;
1244 Number const deltaUSD = fr * 1e4;
1252 XRP(10'000) + depositXRP,
1253 USD(10'000) + depositUSD,
1368 STAmount{USD, UINT64_C(10'000'000001), -6},
1388 for (
auto const& feat : {all, all - fixAMMv1_3})
1390 Env env(*
this, feat);
1481 using namespace jtx;
1484 [&](
AMM& ammAlice,
Env& env) {
1486 .asset1Out =
XRP(100),
1494 [&](
AMM& ammAlice,
Env& env) {
1496 .asset1Out =
USD(100),
1507 env(
fset(
gw_, asfRequireAuth));
1520 .asset1Out =
USD(100),
1556 NotTEC>>
const invalidOptions = {
1568 tfSingleAsset | tfTwoAsset,
1570 {1'000, std::nullopt, std::nullopt, std::nullopt, tfWithdrawAll,
temMALFORMED},
1575 tfWithdrawAll | tfLPToken,
1577 {std::nullopt, std::nullopt,
USD(100), std::nullopt, tfWithdrawAll,
temMALFORMED},
1582 tfWithdrawAll | tfOneAssetWithdrawAll,
1584 {std::nullopt,
USD(100), std::nullopt, std::nullopt, tfWithdrawAll,
temMALFORMED},
1589 tfOneAssetWithdrawAll,
1591 {1'000, std::nullopt,
USD(100), std::nullopt, std::nullopt,
temMALFORMED},
1608 for (
auto const& it : invalidOptions)
1619 Ter(std::get<5>(it)));
1651 tfOneAssetWithdrawAll,
1700 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
1707 [&](
AMM& ammAlice,
Env& env) {
1717 alice_,
IOUAmount{9'999'999'9999, -4}, std::nullopt, std::nullopt, err);
1727 {all, all - fixAMMv1_3});
1730 [&](
AMM& ammAlice,
Env& env) {
1739 alice_,
IOUAmount{9'999'999'999999999, -9}, std::nullopt, std::nullopt, err);
1749 {all, all - fixAMMv1_3});
1759 [&](
AMM& ammAlice,
Env& env) {
1760 auto const fix330 = env.
current()->rules().enabled(fixCleanup3_3_0);
1761 ammAlice.
deposit({.account =
gw_, .asset1In =
USD(1'000), .asset2In =
XRP(1'000)});
1762 env(
fset(
gw_, asfGlobalFreeze));
1765 for (
auto const& account : {
alice_,
gw_})
1770 auto const frozenErr =
1772 ammAlice.
withdraw(account,
USD(100), std::nullopt, std::nullopt, frozenErr);
1773 ammAlice.
withdraw(account, 1'000, std::nullopt, std::nullopt, frozenErr);
1783 [&](
AMM& ammAlice,
Env& env) {
1784 auto const fix330 = env.
current()->rules().enabled(fixCleanup3_3_0);
1792 ammAlice.
withdraw(
alice_, 1'000, std::nullopt, std::nullopt, indivFreezeErr);
1793 ammAlice.
withdraw(
alice_,
USD(100), std::nullopt, std::nullopt, indivFreezeErr);
1827 [&](
AMM& ammAlice,
Env& env) {
1836 {all, all - fixAMMv1_3});
1856 [&](
AMM& ammAlice,
Env&) {
1864 [&](
AMM& ammAlice,
Env&) {
1886 [&](
AMM& ammAlice,
Env& env) {
1892 auto const err = [&] {
1895 if (env.
enabled(fixCleanup3_3_0))
1906 {.features = {all, all - fixAMMv1_3, all - fixCleanup3_3_0}, .noLog =
true});
1950 using namespace jtx;
1955 auto const baseFee = env.
current()->fees().base.drops();
2000 [&](
AMM& ammAlice,
Env& env) {
2016 {all, all - fixAMMv1_3});
2064 [&](
AMM& ammAlice,
Env& env) {
2089 {all, all - fixAMMv1_3});
2131 [&](
AMM& ammAlice,
Env& env) {
2149 else if (env.
enabled(fixAMMv1_3))
2159 {.features = {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3}, .noLog =
true});
2163 [&](
AMM& ammAlice,
Env& env) {
2181 else if (env.
enabled(fixAMMv1_3))
2192 {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3});
2225 STAmount{USD, UINT64_C(9'999'999999), -6},
2229 [&](
AMM& ammAlice,
Env& env) {
2246 {all, all - fixAMMv1_3});
2295 [&](
AMM& ammAlice,
Env& env) {
2296 auto const err = env.
enabled(fixCleanup3_3_0) || env.
enabled(fixCleanup3_4_0)
2302 .asset1Out =
USD(0),
2309 {all - fixCleanup3_3_0 - fixCleanup3_4_0, all - fixCleanup3_4_0, all});
2316 using namespace jtx;
2330 std::nullopt, 1'001, std::nullopt, std::nullopt, std::nullopt,
Ter(
temBAD_FEE));
2363 using namespace jtx;
2368 ammAlice.
vote({}, 1'000);
2374 auto vote = [&](
AMM& ammAlice,
2377 int fundUSD = 100'000,
2387 fund(env,
gw_, {a}, {
USD(fundUSD)}, Fund::Acct);
2389 ammAlice.
vote(a, 50 * (i + 1));
2396 [&](
AMM& ammAlice,
Env& env) {
2397 for (
int i = 0; i < 7; ++i)
2398 vote(ammAlice, env, i, 10'000);
2409 for (
int i = 0; i < 7; ++i)
2410 vote(ammAlice, env, i);
2413 ammAlice.
vote(a, 450);
2420 for (
int i = 0; i < 7; ++i)
2421 vote(ammAlice, env, i);
2423 vote(ammAlice, env, 7, 100'000, 20'000'000);
2430 for (
int i = 7; i > 0; --i)
2431 vote(ammAlice, env, i);
2433 vote(ammAlice, env, 0, 100'000, 20'000'000);
2442 for (
int i = 0; i < 7; ++i)
2443 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2445 for (
int i = 0; i < 7; ++i)
2459 for (
int i = 0; i < 7; ++i)
2460 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2462 for (
int i = 0; i < 7; ++i)
2463 ammAlice.
withdraw(accounts[i], 9'000'000);
2467 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2468 for (
auto const& entry : info)
2469 BEAST_EXPECT(entry[jss::account] !=
carol_.human());
2479 using namespace jtx;
2489 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2496 .bidMin = 1'000'000,
2508 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2515 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2521 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2527 BEAST_EXPECT(
Number{
amm.getLPTokensBalance(
gw_)} == 1);
2544 .flags = tfWithdrawAll,
2550 for (
auto bid : {0, -100})
2599 .authAccounts = {bob_},
2607 .assets = {{USD, GBP}},
2614 .bidMax = STAmount{USD, 100},
2616 Ter(temBAD_AMM_TOKENS));
2619 .bidMin = STAmount{USD, 100},
2621 Ter(temBAD_AMM_TOKENS));
2625 testAMM([&](AMM& ammAlice, Env& env) {
2626 ammAlice.withdrawAll(alice_);
2635 testAMM([&](AMM& ammAlice, Env& env) {
2640 env.fund(
XRP(1'000), bob_, ed, bill, scott, james);
2642 ammAlice.deposit(carol_, 1'000'000);
2646 .authAccounts = {bob_, ed, bill, scott, james},
2652 testAMM([&](AMM& ammAlice, Env& env) {
2653 fund(env, gw_, {bob_},
XRP(1'000), {USD(100)}, Fund::Acct);
2654 ammAlice.deposit(carol_, 1'000'000);
2655 ammAlice.deposit(bob_, 10);
2658 .bidMin = 1'000'001,
2660 Ter(tecAMM_INVALID_TOKENS));
2663 .bidMax = 1'000'001,
2665 Ter(tecAMM_INVALID_TOKENS));
2670 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2675 Ter(tecAMM_INVALID_TOKENS));
2681 fund(env, gw_, {alice_, bob_},
XRP(1'000), {USD(1'000)});
2682 AMM
amm(env, gw_,
XRP(10), USD(1'000));
2683 auto const lpIssue =
amm.lptIssue();
2684 env.trust(STAmount{lpIssue, 100}, alice_);
2685 env.trust(STAmount{lpIssue, 50}, bob_);
2686 env(
pay(gw_, alice_, STAmount{lpIssue, 100}));
2687 env(
pay(gw_, bob_, STAmount{lpIssue, 50}));
2688 env(
amm.bid({.account = alice_, .bidMin = 100}));
2695 Ter(tecAMM_FAILED));
2703 using namespace jtx;
2710 [&](
AMM& ammAlice,
Env& env) {
2712 env(ammAlice.
bid({.account = carol_, .bidMin = 110}));
2725 [&](
AMM& ammAlice,
Env& env) {
2728 env(ammAlice.
bid({.account = carol_, .bidMin = 110, .bidMax = 110}));
2733 env(ammAlice.
bid({.account = alice_, .bidMin = 180, .bidMax = 200}));
2745 [&](
AMM& ammAlice,
Env& env) {
2748 env(ammAlice.
bid({.account = carol_, .bidMin = 110}));
2754 env(ammAlice.
bid({.account = bob_}));
2764 env(ammAlice.
bid({.account = carol_, .bidMax = 600}));
2776 env(ammAlice.
bid({.account = carol_, .bidMin = 100, .bidMax = 600}));
2786 [&](
AMM& ammAlice,
Env& env) {
2791 if (!features[fixAMMv1_3])
2803 env(ammAlice.
bid({.account = carol_, .bidMin = 110})).
close();
2807 env(ammAlice.
bid({.account = bob_}));
2812 env(ammAlice.
bid({.account = carol_}));
2817 env(ammAlice.
bid({.account = bob_}));
2822 env(ammAlice.
bid({.account = carol_, .bidMin = 110})).
close();
2825 if (!features[fixAMMv1_3])
2846 [&](
AMM& ammAlice,
Env& env) {
2851 ammAlice.
deposit(ed, 1'000'000);
2853 ammAlice.
deposit(dan, 500'000);
2858 .authAccounts = {bob_, ed},
2860 auto const slotPrice =
IOUAmount{5'200};
2861 ammTokens -= slotPrice;
2863 if (!features[fixAMMv1_3])
2873 for (
int i = 0; i < 10; ++i)
2883 if (!features[fixAMMv1_1])
2887 STAmount(
USD, UINT64_C(29'499'00572620545), -11));
2894 XRP(13'000),
STAmount(
USD, UINT64_C(13'002'98282151419), -11), ammTokens));
2900 STAmount(
USD, UINT64_C(29'499'00572620544), -11));
2906 if (!features[fixAMMv1_3])
2923 for (
int i = 0; i < 10; ++i)
2925 auto const tokens = ammAlice.
deposit(dan,
USD(100));
2931 if (!features[fixAMMv1_1])
2937 XRP(13'000),
STAmount{
USD, UINT64_C(13'012'92609877019), -11}, ammTokens));
2942 XRP(13'000),
STAmount{
USD, UINT64_C(13'112'92609877019), -11}, ammTokens));
2949 STAmount{USD, UINT64_C(13'012'92609877019), -11},
2954 if (!features[fixAMMv1_3])
2958 STAmount(
USD, UINT64_C(19'490'05672274399), -11));
2964 STAmount(
USD, UINT64_C(19'490'05672274398), -11));
2967 if (!features[fixAMMv1_3])
2978 STAmount{USD, UINT64_C(13'012'92609877024), -11},
2984 if (!features[fixAMMv1_3])
2995 STAmount{USD, UINT64_C(13'112'92609877024), -11},
3002 if (!features[fixAMMv1_3])
3006 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3013 STAmount{USD, UINT64_C(13'012'92609877024), -11},
3023 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3027 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3030 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3034 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3041 STAmount{USD, UINT64_C(13'114'03663044937), -11},
3048 if (!features[fixAMMv1_1])
3052 STAmount(
USD, UINT64_C(29'399'00572620545), -11));
3054 else if (!features[fixAMMv1_3])
3058 STAmount(
USD, UINT64_C(29'399'00572620544), -11));
3061 for (
int i = 0; i < 10; ++i)
3068 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3072 STAmount(
USD, UINT64_C(29'389'06197177128), -11));
3075 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3078 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3082 STAmount(
USD, UINT64_C(29'389'06197177124), -11));
3085 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3092 STAmount(
USD, UINT64_C(29'389'06197177129), -11));
3095 STAmount{USD, UINT64_C(13'123'98038488352), -11},
3103 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3110 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3132 [&](AMM& ammAlice, Env& env) {
3135 env(ammAlice.bid({.account = alice_, .bidMin = IOUAmount{tiny}}));
3138 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
3140 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'000), USD(10'000), ammAlice.tokens()));
3144 .bidMin = IOUAmount{STAmount::kMinValue, STAmount::kMinOffset},
3147 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny * Number{105, -2}}));
3150 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'000), USD(10'000), ammAlice.tokens()));
3159 [&](AMM& ammAlice, Env& env) {
3162 .bidMin = IOUAmount{100},
3163 .authAccounts = {carol_},
3165 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol_}));
3166 env(ammAlice.bid({.account = alice_, .bidMin = IOUAmount{100}}));
3167 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3170 fund(env, {bob, dan},
XRP(1'000));
3173 .bidMin = IOUAmount{100},
3174 .authAccounts = {bob, dan},
3176 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3185 Env env(*
this, features);
3186 fund(env, gw_, {alice_, bob_},
XRP(2'000), {USD(2'000)});
3187 AMM
amm(env, gw_,
XRP(1'000), USD(1'010),
false, 1'000);
3188 auto const lpIssue =
amm.lptIssue();
3189 env.trust(STAmount{lpIssue, 500}, alice_);
3190 env.trust(STAmount{lpIssue, 50}, bob_);
3191 env(
pay(gw_, alice_, STAmount{lpIssue, 500}));
3192 env(
pay(gw_, bob_, STAmount{lpIssue, 50}));
3194 env(
amm.bid({.account = alice_, .bidMin = 500}));
3195 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3196 BEAST_EXPECT(
expectHolding(env, alice_, STAmount{lpIssue, 0}));
3199 env(
pay(alice_, bob_, USD(10)), Path(~USD), Sendmax(
XRP(11)));
3200 BEAST_EXPECT(
amm.expectBalances(
3201 XRPAmount{1'010'010'011}, USD(1'000), IOUAmount{1'004'487'562112089, -9}));
3203 env(
pay(bob_, alice_,
XRP(10)), Path(~XRP), Sendmax(USD(11)));
3204 if (!features[fixAMMv1_1])
3206 BEAST_EXPECT(
amm.expectBalances(
3207 XRPAmount{1'000'010'011},
3208 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3209 IOUAmount{1'004'487'562112089, -9}));
3213 BEAST_EXPECT(
amm.expectBalances(
3214 XRPAmount{1'000'010'011},
3215 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3216 IOUAmount{1'004'487'562112089, -9}));
3222 Env env(*
this, features);
3223 auto const baseFee = env.current()->fees().base;
3225 fund(env, gw_, {alice_, bob_},
XRP(2'000), {USD(2'000)});
3226 AMM
amm(env, gw_,
XRP(1'000), USD(1'010),
false, 1'000);
3227 json::Value const tx =
amm.bid({.account = alice_, .bidMin = 500});
3230 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3231 env.app().config().features.erase(featureAMM);
3232 PreflightContext
const pfCtx(
3233 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3234 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3235 BEAST_EXPECT(pf == temDISABLED);
3236 env.app().config().features.insert(featureAMM);
3240 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3241 jtx.jv[
"TxnSignature"] =
"deadbeef";
3242 jtx.stx = env.ust(jtx);
3243 PreflightContext
const pfCtx(
3244 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3245 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3250 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3251 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3252 jtx.jv[
"Asset2"].removeMember(
"issuer");
3253 jtx.stx = env.ust(jtx);
3254 PreflightContext
const pfCtx(
3255 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3256 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3257 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3266 using namespace jtx;
3278 AMM const ammAlice(env, acct,
XRP(10),
USD(10));
3290 AMM const ammAlice(env, acct,
XRP(1'000'000),
USD(100));
3300 auto const baseFee = env.
current()->fees().base;
3311 auto const pk =
carol_.pk();
3312 auto const settleDelay = 100s;
3327 [&](
AMM& ammAlice,
Env& env) {
3364 env(
fset(
gw_, asfGlobalFreeze));
3368 Txflags(tfPartialPayment | tfNoRippleDirect),
3373 Txflags(tfPartialPayment | tfNoRippleDirect),
3385 Txflags(tfPartialPayment | tfNoRippleDirect),
3390 Txflags(tfPartialPayment | tfNoRippleDirect),
3403 Txflags(tfNoRippleDirect | tfPartialPayment),
3412 using namespace jtx;
3417 [&](
AMM& ammAlice,
Env& env) {
3432 {{
XRP(10'000),
USD(10'100)}},
3439 [&](
AMM& ammAlice,
Env& env) {
3451 {{
XRP(10'000),
USD(10'100)}},
3459 [&](
AMM& ammAlice,
Env& env) {
3471 {{
XRP(10'000),
USD(10'100)}},
3478 [&](
AMM& ammAlice,
Env& env) {
3486 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
3501 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
3505 {{
XRP(10'000),
USD(10'010)}},
3512 [&](
AMM& ammAlice,
Env& env) {
3523 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
3532 {{
XRP(10'000),
USD(10'010)}},
3539 [&](
AMM& ammAlice,
Env& env) {
3548 {{
XRP(10'000),
USD(10'000)}},
3558 Env env(*
this, features);
3572 BEAST_EXPECT(ammEurXrp.expectBalances(
3575 ammEurXrp.tokens()));
3576 if (!features[fixAMMv1_1])
3578 BEAST_EXPECT(ammUsdEur.expectBalances(
3581 ammUsdEur.tokens()));
3584 Amounts const expectedAmounts = env.
closed()->rules().enabled(fixReducedOffersV2)
3593 BEAST_EXPECT(ammUsdEur.expectBalances(
3596 ammUsdEur.tokens()));
3599 Amounts const expectedAmounts = env.
closed()->rules().enabled(fixReducedOffersV2)
3619 [&](
AMM& ammAlice,
Env& env) {
3645 STAmount(
USD, UINT64_C(49'98750312422), -11)}}}));
3664 [&](
AMM& ammAlice,
Env& env) {
3671 if (!features[fixAMMv1_1])
3696 {{
XRP(10'000),
USD(10'100)}},
3705 Env env(*
this, features);
3720 [&](
AMM& ammAlice,
Env& env) {
3733 {{
XRP(10'000),
USD(10'100)}},
3741 [&](
AMM& ammAlice,
Env& env) {
3758 {{
GBP(1'000),
EUR(1'100)}},
3769 if (!features[fixAMMv1_1])
3778 BEAST_EXPECT(
amm.expectBalances(
XRP(1'000),
USD(500),
amm.tokens()));
3781 else if (!features[featureMPTokensV2])
3783 BEAST_EXPECT(
amm.expectBalances(
3794 STAmount(
USD, UINT64_C(29'949'94999999494), -11));
3805 BEAST_EXPECT(
amm.expectBalances(
3818 STAmount(
USD, UINT64_C(29'949'95000060055), -11));
3821 {{
XRP(1'000),
USD(500)}},
3831 if (!features[fixAMMv1_1])
3833 BEAST_EXPECT(
amm.expectBalances(
3839 BEAST_EXPECT(
amm.expectBalances(
3844 {{
XRP(1'000),
USD(500)}},
3850 [&](
AMM& ammAlice,
Env& env) {
3866 if (!features[fixAMMv1_1])
3875 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
3876 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
3885 STAmount{
GBP, UINT64_C(50'684828792831), -12}}}));
3910 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
3911 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
3920 STAmount{
GBP, UINT64_C(27'06583722134028), -14}}}));
3940 {{
GBP(1'000),
EUR(1'100)}},
3953 [&](
AMM& ammAlice,
Env& env) {
3966 {{
GBP(1'000),
EUR(1'100)}},
3983 [&](
AMM& ammAlice,
Env& env) {
3986 auto const can =
gw_[
"CAN"];
3987 fund(env,
gw_, {dan}, {can(200),
GBP(200)}, Fund::Acct);
3989 fund(env,
gw_, {
bob_}, {can(195.3125)}, Fund::Acct);
3993 env(
offer(dan, can(200),
GBP(200)));
4007 {{
GBP(10'000),
EUR(10'125)}},
4014 [&](
AMM& ammAlice,
Env& env) {
4043 Env env(*
this, features);
4044 auto const eth =
gw_[
"ETH"];
4050 {
EUR(50'000),
BTC(50'000), eth(50'000),
USD(50'000)});
4056 AMM const xrpEth(env,
alice_,
XRP(10'000), eth(10'100));
4057 AMM const ethEur(env,
alice_, eth(10'900),
EUR(11'000));
4064 if (!features[fixAMMv1_1])
4070 STAmount{eth, UINT64_C(10'073'65779244494), -11},
4073 STAmount{eth, UINT64_C(10'926'34220755506), -11},
4074 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4077 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4078 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4091 STAmount{eth, UINT64_C(10'073'65779244461), -11},
4094 STAmount{eth, UINT64_C(10'926'34220755539), -11},
4095 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4098 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4099 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4115 BEAST_EXPECT(xrpEur.expectBalances(
XRP(10'100),
EUR(10'000), xrpEur.tokens()));
4124 Env env(*
this, features);
4125 auto const eth =
gw_[
"ETH"];
4131 {
EUR(50'000),
BTC(50'000), eth(50'000),
USD(50'000)});
4136 AMM const xrpEth(env,
alice_,
XRP(10'000), eth(10'100));
4137 AMM const ethEur(env,
alice_, eth(10'900),
EUR(11'000));
4142 if (!features[fixAMMv1_1])
4146 BEAST_EXPECT(xrpEur.expectBalances(
4151 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4152 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4155 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
USD(9'900), btcUsd.
tokens()));
4158 STAmount{eth, UINT64_C(10'017'41072778012), -11},
4161 STAmount{eth, UINT64_C(10'982'58927221988), -11},
4162 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4167 BEAST_EXPECT(xrpEur.expectBalances(
4172 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4173 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4176 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
USD(9'900), btcUsd.
tokens()));
4179 STAmount{eth, UINT64_C(10'017'41072777996), -11},
4182 STAmount{eth, UINT64_C(10'982'58927222004), -11},
4183 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4192 [&](
AMM& ammAlice,
Env& env) {
4196 for (
int i = 0; i < 30; ++i)
4204 Txflags(tfPartialPayment | tfNoRippleDirect));
4205 if (!features[fixAMMv1_1])
4232 [&](
AMM& ammAlice,
Env& env) {
4236 for (
int i = 0; i < 29; ++i)
4244 Txflags(tfPartialPayment | tfNoRippleDirect));
4247 if (!features[fixAMMv1_1])
4271 Env env(*
this, features);
4276 if (!features[fixAMMv1_1])
4280 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4292 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4305 [&](
AMM& ammAlice,
Env& env) {
4312 Txflags(tfNoRippleDirect | tfPartialPayment),
4325 using namespace jtx;
4329 auto const baseFee = env.
current()->fees().base.drops();
4330 auto const token1 = ammAlice.
lptIssue();
4351 env(ammAlice.
bid({.account = carol_, .bidMin = 100}));
4376 ammAlice1.deposit(
carol_, 1'000'000);
4377 auto const token1 = ammAlice.
lptIssue();
4378 auto const token2 = ammAlice1.lptIssue();
4397 auto const token1 = ammAlice.
lptIssue();
4429 using namespace jtx;
4450 using namespace jtx;
4453 auto const info = env.
rpc(
4457 auto const flags = info[jss::result][jss::account_data][jss::Flags].
asUInt();
4458 BEAST_EXPECT(flags == (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
4466 using namespace jtx;
4482 auto const tsta = a[
"TST"];
4483 auto const tstb = b[
"TST"];
4492 env.
trust(tsta(10'000), c);
4493 env.
trust(tstb(10'000), c);
4494 env(
pay(a, c, tsta(10'000)));
4495 env(
pay(b, c, tstb(10'000)));
4496 AMM const amm(env, c, tsta(5'000), tstb(5'000));
4497 auto const ammIss =
Issue(tsta.currency,
amm.ammAccount());
4507 Txflags(tfPartialPayment | tfNoRippleDirect),
4515 testcase(
"AMMAndCLOB, offer quality change");
4516 using namespace jtx;
4517 auto const gw =
Account(
"gw");
4518 auto const tst = gw[
"TST"];
4519 auto const lP1 =
Account(
"LP1");
4520 auto const lP2 =
Account(
"LP2");
4522 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4523 Env env(*
this, features);
4524 env.
fund(
XRP(30'000'000'000), gw);
4525 env(
offer(gw,
XRP(11'500'000'000), tst(1'000'000'000)));
4546 [&](
Env& env) {
AMM const amm(env, lP1, tst(25),
XRP(250)); },
4548 lp2TSTBalance =
getAccountLines(env, lP2, tst)[
"lines"][0u][
"balance"].asString();
4550 lp2TakerGets =
offer[
"taker_gets"].asString();
4551 lp2TakerPays =
offer[
"taker_pays"][
"value"].asString();
4556 if (!features[fixAMMv1_1])
4561 STAmount{tst, UINT64_C(1'68737984885388), -14}),
4569 STAmount{tst, UINT64_C(1'68737976189735), -14}),
4578 BEAST_EXPECT(lp2TakerGets ==
offer[
"taker_gets"].asString());
4579 BEAST_EXPECT(lp2TakerPays ==
offer[
"taker_pays"][
"value"].asString());
4587 using namespace jtx;
4591 [&](
AMM& ammAlice,
Env& env) {
4613 {{
USD(1'000),
EUR(1'000)}},
4621 [&](
AMM& ammAlice,
Env& env) {
4632 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4633 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4643 [&](
AMM& ammAlice,
Env& env) {
4645 auto const tokensFee =
4654 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4655 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4664 [&](
AMM& ammAlice,
Env& env) {
4678 {{
USD(1'000),
EUR(1'000)}},
4685 [&](
AMM& ammAlice,
Env& env) {
4687 auto const tokensFee =
4690 auto const balanceAfterWithdraw = [&]() {
4691 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4693 return STAmount(
USD, UINT64_C(30'443'43891402715), -11);
4695 if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4697 return STAmount(
USD, UINT64_C(30'443'43891402714), -11);
4700 return STAmount(
USD, UINT64_C(30'443'43891402713), -11);
4704 auto const deposit = balanceAfterWithdraw -
USD(29'000);
4710 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4714 STAmount(
USD, UINT64_C(30'443'43891402717), -11));
4716 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4720 STAmount(
USD, UINT64_C(30'443'43891402716), -11));
4726 STAmount(
USD, UINT64_C(30'443'43891402713), -11));
4730 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4732 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4734 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4736 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4740 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779911, -8));
4742 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4751 [&](
AMM& ammAlice,
Env& env) {
4787 {{
USD(1'000),
EUR(1'010)}},
4794 [&](
AMM& ammAlice,
Env& env) {
4820 STAmount{
USD, UINT64_C(5'025125628140703), -15}}}}));
4821 if (!features[fixAMMv1_1])
4824 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4825 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4831 STAmount{USD, UINT64_C(1'004'97487437186), -11},
4832 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4836 {{
USD(1'000),
EUR(1'010)}},
4846 Env env(*
this, features);
4853 if (!features[fixAMMv1_1])
4873 Env env(*
this, features);
4881 if (!features[fixAMMv1_1])
4906 Env env(*
this, features);
4911 AMM const ammAlice(env,
alice_,
USD(1'005),
EUR(1'000),
false, 1'000);
4924 Env env(*
this, features);
4929 AMM const ammAlice(env,
alice_,
USD(1'005),
EUR(1'000),
false, 1'000);
4943 testcase(
"Adjusted Deposit/Withdraw Tokens");
4945 using namespace jtx;
4949 [&](
AMM& ammAlice,
Env& env) {
4957 Account const natalie(
"natalie");
4961 {bob, ed, paul, dan, chris, simon, ben, natalie},
4964 for (
int i = 0; i < 10; ++i)
4988 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4995 else if (features[fixAMMv1_3])
5011 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5016 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5026 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5029 env, natalie,
STAmount{
USD, UINT64_C(1'500'000'000000002), -9}));
5031 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5034 env, natalie,
STAmount{
USD, UINT64_C(1'500'000'000000005), -9}));
5042 if (!features[fixAMMv1_1])
5047 else if (features[fixAMMv1_3])
5069 [&](
AMM& ammAlice,
Env& env) {
5077 Account const natalie(
"natalie");
5081 {bob, ed, paul, dan, chris, simon, ben, natalie},
5085 for (
int i = 0; i < 10; ++i)
5106 auto const baseFee = env.
current()->fees().base.drops();
5107 if (!features[fixAMMv1_3])
5115 auto const xrpBalance = (
XRP(2'000'000) -
txFee(env, 20)).getText();
5140 auto const xrpBalance =
XRP(2'000'000) -
txFee(env, 20) -
drops(10);
5141 auto const xrpBalanceText = xrpBalance.getText();
5168 using namespace jtx;
5191 BEAST_EXPECT(
amm.ammExists());
5201 std::nullopt, 100, std::nullopt, std::nullopt, std::nullopt,
Ter(
tecAMM_EMPTY));
5218 BEAST_EXPECT(
amm.expectBalances(
XRP(10'000),
USD(10'000),
amm.tokens()));
5219 BEAST_EXPECT(
amm.expectTradingFee(1'000));
5220 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5225 BEAST_EXPECT(!
amm.ammExists());
5248 BEAST_EXPECT(
amm.ammExists());
5252 BEAST_EXPECT(
amm.ammExists());
5255 BEAST_EXPECT(!
amm.ammExists());
5267 using namespace jtx;
5279 using namespace jtx;
5281 amm.setClose(
false);
5282 auto const info = env.
rpc(
5289 info[jss::result][jss::account_data][jss::AMMID].asString() ==
5302 for (
auto const& node : affected)
5304 if (node.isMember(sfModifiedNode.fieldName) &&
5305 node[sfModifiedNode.fieldName][sfLedgerEntryType.fieldName].asString() ==
5307 node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::Account]
5310 found = node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::AMMID]
5315 BEAST_EXPECT(found);
5327 testcase(
"Offer/Strand Selection");
5328 using namespace jtx;
5331 auto const eth = gw1[
"ETH"];
5332 auto const can = gw1[
"CAN"];
5338 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5343 env(
rate(gw1, gw1Rate));
5361 for (
auto i = 0; i < 3; ++i)
5363 Env env(*
this, features);
5364 prep(env, rates.first, rates.second);
5366 if (i == 0 || i == 2)
5372 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5378 BEAST_EXPECT(
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5387 BEAST_EXPECT(q[0] > q[1]);
5389 BEAST_EXPECT(q[0] == q[2]);
5396 for (
auto i = 0; i < 3; ++i)
5398 Env env(*
this, features);
5399 prep(env, rates.first, rates.second);
5401 if (i == 0 || i == 2)
5407 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5413 BEAST_EXPECT(
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5415 if (i == 0 || i == 2)
5434 for (
auto i = 0; i < 3; ++i)
5436 Env env(*
this, features);
5437 prep(env, rates.first, rates.second);
5439 if (i == 0 || i == 2)
5445 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5451 BEAST_EXPECT(!
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5453 if (i == 2 && !features[fixAMMv1_1])
5455 if (rates.first == 1.5)
5457 if (!features[fixAMMv1_1])
5464 STAmount{eth, UINT64_C(378'6327949540823), -13},
5465 STAmount{
USD, UINT64_C(283'9745962155617), -13}}}}));
5474 STAmount{eth, UINT64_C(378'6327949540813), -13},
5475 STAmount{
USD, UINT64_C(283'974596215561), -12}}}}));
5480 if (!features[fixAMMv1_1])
5487 STAmount{eth, UINT64_C(325'299461620749), -12},
5488 STAmount{
USD, UINT64_C(243'9745962155617), -13}}}}));
5497 STAmount{eth, UINT64_C(325'299461620748), -12},
5498 STAmount{
USD, UINT64_C(243'974596215561), -12}}}}));
5504 if (rates.first == 1.5)
5511 STAmount{eth, UINT64_C(378'6327949540812), -13},
5512 STAmount{
USD, UINT64_C(283'9745962155609), -13}}}}));
5521 STAmount{eth, UINT64_C(325'2994616207479), -13},
5522 STAmount{
USD, UINT64_C(243'9745962155609), -13}}}}));
5532 BEAST_EXPECT(q[1] > q[0]);
5534 BEAST_EXPECT(q[2] > q[1]);
5538 for (
auto i = 0; i < 3; ++i)
5540 Env env(*
this, features);
5541 prep(env, rates.first, rates.second);
5543 if (i == 0 || i == 2)
5549 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5555 BEAST_EXPECT(!
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5561 if (rates.first == 1.5)
5563 if (!features[fixAMMv1_1])
5572 STAmount{eth, UINT64_C(64'91106406735152), -14},
5585 STAmount{eth, UINT64_C(335'0889359326475), -13},
5593 if (!features[fixAMMv1_1])
5601 STAmount{eth, UINT64_C(335'0889359326485), -13},
5614 STAmount{eth, UINT64_C(335'0889359326475), -13},
5641 for (
auto i = 0; i < 3; ++i)
5643 Env env(*
this, features);
5644 prep(env, rates.first, rates.second);
5647 if (i == 0 || i == 2)
5649 env(
offer(ed, eth(400), can(400)),
Txflags(tfPassive));
5655 amm.emplace(env, ed, eth(1'000),
USD(1'000));
5665 if (i == 2 && !features[fixAMMv1_1])
5667 if (rates.first == 1.5)
5670 BEAST_EXPECT(
amm->expectBalances(
5671 STAmount{eth, UINT64_C(1'176'66038955758), -11},
5677 BEAST_EXPECT(
amm->expectBalances(
5678 STAmount{eth, UINT64_C(1'179'540094339627), -12},
5679 STAmount{USD, UINT64_C(847'7880529867501), -13},
5686 STAmount{eth, UINT64_C(343'3179205198749), -13},
5687 STAmount{can, UINT64_C(343'3179205198749), -13},
5690 STAmount{can, UINT64_C(362'2119470132499), -13},
5697 if (rates.first == 1.5)
5700 BEAST_EXPECT(
amm->expectBalances(
5701 STAmount{eth, UINT64_C(1'176'660389557593), -12},
5707 BEAST_EXPECT(
amm->expectBalances(
5708 STAmount{eth, UINT64_C(1'179'54009433964), -11},
5709 STAmount{USD, UINT64_C(847'7880529867501), -13},
5716 STAmount{eth, UINT64_C(343'3179205198749), -13},
5717 STAmount{can, UINT64_C(343'3179205198749), -13},
5720 STAmount{can, UINT64_C(362'2119470132499), -13},
5730 BEAST_EXPECT(q[1] > q[0]);
5731 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5739 testcase(
"Fix Default Inner Object");
5740 using namespace jtx;
5751 Env env(*
this, features);
5753 AMM amm(env,
gw_,
XRP(10),
USD(10), {.tfee = tfee, .close = closeLedger});
5782 testcase(
"Fix changeSpotPriceQuality");
5783 using namespace jtx;
5788 SucceedShouldSucceedResize,
5805 {
"0.001519763260828713",
"1558701",
Quality{5414253689393440221}, 1000, FailShouldSucceed},
5806 {
"0.01099814367603737",
"1892611",
Quality{5482264816516900274}, 1000, FailShouldSucceed},
5807 {
"0.78",
"796599",
Quality{5630392334958379008}, 1000, FailShouldSucceed},
5808 {
"105439.2955578965",
"49398693",
Quality{5910869983721805038}, 400, FailShouldSucceed},
5809 {
"12408293.23445213",
"4340810521",
Quality{5911611095910090752}, 997, FailShouldSucceed},
5810 {
"1892611",
"0.01099814367603737",
Quality{6703103457950430139}, 1000, FailShouldSucceed},
5811 {
"423028.8508101858",
"3392804520",
Quality{5837920340654162816}, 600, FailShouldSucceed},
5812 {
"44565388.41001027",
"73890647",
Quality{6058976634606450001}, 1000, FailShouldSucceed},
5813 {
"66831.68494832662",
"16",
Quality{6346111134641742975}, 0, FailShouldSucceed},
5814 {
"675.9287302203422",
"1242632304",
Quality{5625960929244093294}, 300, FailShouldSucceed},
5815 {
"7047.112186735699",
"1649845866",
Quality{5696855348026306945}, 504, FailShouldSucceed},
5816 {
"840236.4402981238",
"47419053",
Quality{5982561601648018688}, 499, FailShouldSucceed},
5817 {
"992715.618909774",
"189445631733",
Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
5818 {
"504636667521",
"185545883.9506651",
Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
5819 {
"992706.7218636649",
"189447316000",
Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
5820 {
"1.068737911388205",
"127860278877",
Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
5821 {
"17932506.56880419",
"189308.6043676173",
Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
5822 {
"1.066379294658174",
"128042251493",
Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
5823 {
"350131413924",
"1576879.110907892",
Quality{6487411636539049449}, 650,
Fail},
5824 {
"422093460",
"2.731797662057464",
Quality{6702911108534394924}, 1000,
Fail},
5825 {
"76128132223",
"367172.7148422662",
Quality{6487263463413514240}, 548,
Fail},
5826 {
"132701839250",
"280703770.7695443",
Quality{6273750681188885075}, 562,
Fail},
5827 {
"994165.7604612011",
"189551302411",
Quality{5697835592690668727}, 815,
Fail},
5828 {
"45053.33303227917",
"86612695359",
Quality{5625695218943638190}, 500,
Fail},
5829 {
"199649.077043865",
"14017933007",
Quality{5766034667318524880}, 324,
Fail},
5830 {
"27751824831.70903",
"78896950",
Quality{6272538159621630432}, 500,
Fail},
5831 {
"225.3731275781907",
"156431793648",
Quality{5477818047604078924}, 989,
Fail},
5832 {
"199649.077043865",
"14017933007",
Quality{5766036094462806309}, 324,
Fail},
5833 {
"3.590272027140361",
"20677643641",
Quality{5406056147042156356}, 808,
Fail},
5834 {
"1.070884664490231",
"127604712776",
Quality{5268620608623825741}, 293,
Fail},
5835 {
"3272.448829820197",
"6275124076",
Quality{5625710328924117902}, 81,
Fail},
5836 {
"0.009059512633902926",
"7994028",
Quality{5477511954775533172}, 1000,
Fail},
5839 {
"10",
"10.0",
Quality{xrpIouAmounts10100}, 100,
Fail},
5840 {
"10.0",
"10",
Quality{iouXrpAmounts10100}, 100,
Fail},
5841 {
"69864389131",
"287631.4543025075",
Quality{6487623473313516078}, 451, Succeed},
5842 {
"4328342973",
"12453825.99247381",
Quality{6272522264364865181}, 997, Succeed},
5843 {
"32347017",
"7003.93031579449",
Quality{6347261126087916670}, 1000, Succeed},
5844 {
"61697206161",
"36631.4583206413",
Quality{6558965195382476659}, 500, Succeed},
5845 {
"1654524979",
"7028.659825511603",
Quality{6487551345110052981}, 504, Succeed},
5846 {
"88621.22277293179",
"5128418948",
Quality{5766347291552869205}, 380, Succeed},
5847 {
"1892611",
"0.01099814367603737",
Quality{6703102780512015436}, 1000, Succeed},
5848 {
"4542.639373338766",
"24554809",
Quality{5838994982188783710}, 0, Succeed},
5849 {
"5132932546",
"88542.99750172683",
Quality{6419203342950054537}, 380, Succeed},
5850 {
"78929964.1549083",
"1506494795",
Quality{5986890029845558688}, 589, Succeed},
5851 {
"10096561906",
"44727.72453735605",
Quality{6487455290284644551}, 250, Succeed},
5852 {
"5092.219565514988",
"8768257694",
Quality{5626349534958379008}, 503, Succeed},
5853 {
"1819778294",
"8305.084302902864",
Quality{6487429398998540860}, 415, Succeed},
5854 {
"6970462.633911943",
"57359281",
Quality{6054087899185946624}, 850, Succeed},
5855 {
"3983448845",
"2347.543644281467",
Quality{6558965195382476659}, 856, Succeed},
5859 {
"771493171",
"1.243473020567508",
Quality{6707566798038544272}, 100, SucceedShouldFail},
5863 boost::regex
const rx(
"^\\d+$");
5864 boost::smatch match;
5868 auto rules = env.
current()->rules();
5872 for (
auto const& t :
tests)
5879 auto const& quality = std::get<Quality>(t);
5880 auto const tfee = std::get<std::uint16_t>(t);
5881 auto const status = std::get<Status>(t);
5882 auto const poolInIsXRP = boost::regex_search(std::get<0>(t), match, rx);
5883 auto const poolOutIsXRP = boost::regex_search(std::get<1>(t), match, rx);
5884 assert(!(poolInIsXRP && poolOutIsXRP));
5885 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
5886 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
5893 if (status == SucceedShouldSucceedResize)
5895 if (!features[fixAMMv1_1])
5897 BEAST_EXPECT(
Quality{*amounts} < quality);
5901 BEAST_EXPECT(
Quality{*amounts} >= quality);
5904 else if (status == Succeed)
5906 if (!features[fixAMMv1_1])
5909 Quality{*amounts} >= quality ||
5914 BEAST_EXPECT(
Quality{*amounts} >= quality);
5917 else if (status == FailShouldSucceed)
5919 BEAST_EXPECT(features[fixAMMv1_1] &&
Quality{*amounts} >= quality);
5921 else if (status == SucceedShouldFail)
5924 !features[fixAMMv1_1] &&
Quality{*amounts} < quality &&
5936 auto tinyOffer = [&]() {
5951 auto const takerPays =
5956 BEAST_EXPECT(
Quality(tinyOffer) < quality);
5958 else if (status == FailShouldSucceed)
5960 BEAST_EXPECT(!features[fixAMMv1_1]);
5962 else if (status == SucceedShouldFail)
5964 BEAST_EXPECT(features[fixAMMv1_1]);
5970 BEAST_EXPECT(!
strcmp(e.
what(),
"changeSpotPriceQuality failed"));
5971 BEAST_EXPECT(!features[fixAMMv1_1] && status == FailShouldSucceed);
5979 BEAST_EXPECT(!res.has_value());
5986 using namespace jtx;
5990 .flags = tfSingleAsset,
5998 .flags = tfOneAssetLPToken,
6006 .flags = tfLimitLPToken,
6014 .asset1Out =
XRP(100),
6015 .asset2Out =
XRP(100),
6023 .asset1Out =
XRP(100),
6024 .asset2Out =
BAD(100),
6032 jv[jss::TransactionType] = jss::AMMWithdraw;
6033 jv[jss::Flags] = tfLimitLPToken;
6034 jv[jss::Account] =
alice_.human();
6036 XRP(100).value().setJson(jv[jss::Amount]);
6037 USD(100).value().setJson(jv[jss::EPrice]);
6046 using namespace jtx;
6052 Account const gatehub{
"gatehub"};
6053 Account const bitstamp{
"bitstamp"};
6054 Account const trader{
"trader"};
6055 auto const usdGH = gatehub[
"USD"];
6056 auto const btcGH = gatehub[
"BTC"];
6057 auto const usdBIT = bitstamp[
"USD"];
6061 char const* testCase;
6062 double const poolUsdBIT;
6063 double const poolUsdGH;
6076 double const offer1BtcGH = 0.1;
6077 double const offer2BtcGH = 0.1;
6078 double const offer2UsdGH = 1;
6079 double const rateBIT = 0.0;
6080 double const rateGH = 0.0;
6085 for (
auto const& input : {
6087 .testCase =
"Test Fix Overflow Offer",
6090 .sendMaxUsdBIT{usdBIT(50)},
6091 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6094 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6095 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6096 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6097 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6098 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6099 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6100 .lpTokenBalance = {28'61817604250837, -14},
6101 .lpTokenBalanceAlt =
IOUAmount{28'61817604250836, -14},
6109 .testCase =
"Overflow test {1, 100, 0.111}",
6112 .sendMaxUsdBIT{usdBIT(0.111)},
6113 .sendUsdGH{usdGH, 100},
6116 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6117 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6118 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6119 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6120 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6121 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6122 .lpTokenBalance{10, 0},
6123 .offer1BtcGH = 1e-5,
6125 .offer2UsdGH = 1e-5,
6130 .testCase =
"Overflow test {1, 100, 1.00}",
6133 .sendMaxUsdBIT{usdBIT(1.00)},
6134 .sendUsdGH{usdGH, 100},
6137 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6138 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6139 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6140 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6141 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6142 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6143 .lpTokenBalance{10, 0},
6144 .offer1BtcGH = 1e-5,
6146 .offer2UsdGH = 1e-5,
6151 .testCase =
"Overflow test {1, 100, 4.6432}",
6154 .sendMaxUsdBIT{usdBIT(4.6432)},
6155 .sendUsdGH{usdGH, 100},
6158 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6159 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6160 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6161 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6162 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6163 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6164 .lpTokenBalance{10, 0},
6165 .offer1BtcGH = 1e-5,
6167 .offer2UsdGH = 1e-5,
6172 .testCase =
"Overflow test {1, 100, 10}",
6175 .sendMaxUsdBIT{usdBIT(10)},
6176 .sendUsdGH{usdGH, 100},
6179 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6180 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6181 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6182 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6183 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6184 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6185 .lpTokenBalance{10, 0},
6186 .offer1BtcGH = 1e-5,
6188 .offer2UsdGH = 1e-5,
6193 .testCase =
"Overflow test {50, 100, 5.55}",
6196 .sendMaxUsdBIT{usdBIT(5.55)},
6197 .sendUsdGH{usdGH, 100},
6200 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6201 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6202 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6203 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6204 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6205 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6206 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6207 .offer1BtcGH = 1e-5,
6209 .offer2UsdGH = 1e-5,
6214 .testCase =
"Overflow test {50, 100, 50.00}",
6217 .sendMaxUsdBIT{usdBIT(50.00)},
6218 .sendUsdGH{usdGH, 100},
6219 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6220 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6221 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6222 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6223 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6224 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6225 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6226 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6227 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6228 .offer1BtcGH = 1e-5,
6230 .offer2UsdGH = 1e-5,
6235 .testCase =
"Overflow test {50, 100, 232.16}",
6238 .sendMaxUsdBIT{usdBIT(232.16)},
6239 .sendUsdGH{usdGH, 100},
6242 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6243 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6244 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6245 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6246 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6247 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6248 .lpTokenBalance{70'71067811865475, -14},
6249 .offer1BtcGH = 1e-5,
6251 .offer2UsdGH = 1e-5,
6256 .testCase =
"Overflow test {50, 100, 500}",
6259 .sendMaxUsdBIT{usdBIT(500)},
6260 .sendUsdGH{usdGH, 100},
6263 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6264 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6265 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6266 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6267 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6268 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6269 .lpTokenBalance{70'71067811865475, -14},
6270 .offer1BtcGH = 1e-5,
6272 .offer2UsdGH = 1e-5,
6279 for (
auto const& features : {all - fixAMMv1_1 - fixAMMv1_3, all})
6283 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6286 if (input.rateGH != 0.0)
6287 env(
rate(gatehub, input.rateGH));
6288 if (input.rateBIT != 0.0)
6289 env(
rate(bitstamp, input.rateBIT));
6291 env(
trust(trader, usdGH(10'000'000)));
6292 env(
trust(trader, usdBIT(10'000'000)));
6293 env(
trust(trader, btcGH(10'000'000)));
6296 env(
pay(gatehub, trader, usdGH(100'000)));
6297 env(
pay(gatehub, trader, btcGH(100'000)));
6298 env(
pay(bitstamp, trader, usdBIT(100'000)));
6301 AMM const amm{env, trader, usdGH(input.poolUsdGH), usdBIT(input.poolUsdBIT)};
6304 IOUAmount const preSwapLPTokenBalance =
amm.getLPTokensBalance();
6306 env(
offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6307 env(
offer(trader, btcGH(input.offer2BtcGH), usdGH(input.offer2UsdGH)));
6310 env(
pay(trader, trader, input.sendUsdGH),
6312 Path(~btcGH, ~usdGH),
6317 auto const failUsdGH = features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6318 auto const failUsdBIT = features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6319 auto const goodUsdGH = features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6320 auto const goodUsdBIT = features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6321 auto const lpTokenBalance = [&] {
6322 if (not env.
enabled(fixAMMv1_3))
6323 return input.lpTokenBalance;
6325 return input.lpTokenBalanceAlt.value_or(input.lpTokenBalance);
6329 BEAST_EXPECT(
amm.expectBalances(goodUsdGH, goodUsdBIT, lpTokenBalance));
6333 BEAST_EXPECT(
amm.getLPTokensBalance() == preSwapLPTokenBalance);
6337 Number const sqrtPoolProduct =
root2(goodUsdGH * goodUsdBIT);
6347 BEAST_EXPECT((sqrtPoolProduct +
Number{1, -14} >= input.lpTokenBalance));
6357 using namespace jtx;
6359 STAmount const xrpPool{
XRP, UINT64_C(51600'000981)};
6360 STAmount const iouPool{
USD, UINT64_C(803040'9987141784), -10};
6362 STAmount const xrpBob{
XRP, UINT64_C(1092'878933)};
6363 STAmount const iouBob{
USD, UINT64_C(3'988035892323031), -28};
6368 auto [xrpBegin, iouBegin, lptBegin] =
amm.balances(
XRP,
USD);
6380 BEAST_EXPECT(
amm.expectBalances(xrpBegin, iouBegin,
amm.tokens()));
6382 {{xrpPool, iouPool}},
6391 testcase(
"AMM Offer Blocked By LOB");
6392 using namespace jtx;
6398 Env env(*
this, features);
6412 if (!features[fixAMMv1_1])
6414 BEAST_EXPECT(
amm.expectBalances(
XRP(200'000),
USD(100'000),
amm.tokens()));
6432 Env env(*
this, features);
6453 Env env(*
this, features);
6463 if (!features[fixAMMv1_1])
6465 BEAST_EXPECT(
amm.expectBalances(
XRP(1'000),
USD(500),
amm.tokens()));
6469 else if (!features[featureMPTokensV2])
6471 BEAST_EXPECT(
amm.expectBalances(
6484 BEAST_EXPECT(
amm.expectBalances(
6500 Env env(*
this, features);
6506 if (!features[featureMPTokensV2])
6508 BEAST_EXPECT(
amm.expectBalances(
6520 BEAST_EXPECT(
amm.expectBalances(
6537 using namespace jtx;
6551 auto const lpToken =
6553 auto const lpTokenBalance =
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6554 BEAST_EXPECT(lpToken ==
"1414.213562373095" && lpTokenBalance ==
"1414.213562373");
6555 if (!features[fixAMMv1_1])
6558 BEAST_EXPECT(
amm.ammExists());
6563 BEAST_EXPECT(!
amm.ammExists());
6569 for (
auto const& lp : {
gw_,
bob_})
6571 Env env(*
this, features);
6572 auto const abc =
gw_[
"ABC"];
6578 {
USD(1'000'000'000), abc(1'000'000'000'000)});
6579 AMM amm(env, lp, abc(2'000'000),
USD(1));
6584 auto const lpToken =
6586 auto const lpTokenBalance =
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6587 BEAST_EXPECT(lpToken ==
"1414.213562373095" && lpTokenBalance ==
"1414.213562373");
6588 if (!features[fixAMMv1_1])
6591 BEAST_EXPECT(
amm.ammExists());
6595 amm.withdrawAll(lp);
6596 BEAST_EXPECT(!
amm.ammExists());
6603 Env env(*
this, features);
6608 BEAST_EXPECT(res && !res.value());
6610 BEAST_EXPECT(res && !res.value());
6614 Env env(*
this, features);
6619 BEAST_EXPECT(res && !res.value());
6621 BEAST_EXPECT(res && !res.value());
6625 Env env(*
this, features);
6627 auto const yan = gw1[
"YAN"];
6629 fund(env, gw1, {
gw_},
XRP(1'000), {yan(1'000)}, Fund::TokenOnly);
6633 BEAST_EXPECT(res && !res.value());
6635 BEAST_EXPECT(res && !res.value());
6642 testcase(
"test clawback from AMM account");
6643 using namespace jtx;
6646 Env env(*
this, features);
6648 env(
fset(
gw_, asfAllowTrustLineClawback));
6654 if (!features[featureAMMClawback])
6658 env(
fclear(
gw_, asfAllowTrustLineClawback));
6683 Issue const usd(
USD.issue().currency,
amm.ammAccount());
6685 env(
claw(
gw_, amount), error);
6692 testcase(
"test AMMDeposit with frozen assets");
6693 using namespace jtx;
6710 Env env(*
this, features);
6711 testAMMDeposit(env, [&](
AMM&
amm) {
6719 Env env(*
this, features);
6720 testAMMDeposit(env, [&](
AMM&
amm) {
6726 if (features[featureAMMClawback] || features[fixCleanup3_3_0])
6731 Env env(*
this, features);
6732 testAMMDeposit(env, [&](
AMM&
amm) {
6742 Env env(*
this, features);
6743 testAMMDeposit(env, [&](
AMM&
amm) {
6753 testcase(
"Fix Reserve Check On Withdrawal");
6754 using namespace jtx;
6758 auto test = [&](
auto&& cb) {
6759 Env env(*
this, features);
6760 auto const startingXrp =
reserve(env, 2) + env.
current()->fees().base * 5;
6779 .account =
alice_, .asset1Out =
EUR(0.1), .asset2Out =
USD(0.1), .err = err});
6782 .account =
alice_, .asset1Out =
USD(0.1), .asset2Out =
EUR(0.1), .err = err});
6798 testcase(
"Fail pseudo-account allocation " + suffix);
6810 for (
int i = 0; i < 256; ++i)
6835 testcase(
"Deposit and Withdraw Rounding V2");
6836 using namespace jtx;
6838 auto const xpm =
gw_[
"XPM"];
6840 STAmount xpmBalance{xpm, UINT64_C(18'610'359'80246901), -8};
6841 STAmount amount{xpm, UINT64_C(6'566'496939465400), -12};
6845 Env env(*
this, features);
6853 STAmount const lptAMMBalance{
amm.lptIssue(), UINT64_C(3'234'987'266'485968), -6};
6854 auto const burn =
IOUAmount{
amm.getLPTokensBalance() - lptAMMBalance};
6856 env(
amm.bid(
BidArg{.account = gw_, .bidMin = burn, .bidMax = burn}));
6867 auto const [amount, amount2, lptAMM] =
amm.balances(
XRP, xpm);
6868 auto const withdraw =
STAmount{xpm, 1, -5};
6870 auto const [amount_, amount2_, lptAMM_] =
amm.balances(
XRP, xpm);
6873 BEAST_EXPECT((amount2 - amount2_) > withdraw);
6877 BEAST_EXPECT((amount2 - amount2_) <= withdraw);
6886 auto const [amount, amount2, lptBalance] =
amm.balances(
GBP,
EUR);
6891 auto const res =
root2(amount * amount2);
6895 BEAST_EXPECT(res < lptBalance);
6899 BEAST_EXPECT(res >= lptBalance);
6907 using namespace jtx;
6919 [&](
AMM& ammAlice,
Env& env) {
6925 {
GBP(100'000),
EUR(100'000)},
6936 {{
GBP(30'000),
EUR(30'000)}},
6944 [&](
AMM& ammAlice,
Env& env) {
6948 STAmount const depositEuro{
EUR, UINT64_C(10'1234567890123456), -16};
6949 STAmount const depositGBP{
GBP, UINT64_C(10'1234567890123456), -16};
6952 DepositArg{.account =
bob_, .asset1In = depositEuro, .asset2In = depositGBP});
6953 invariant(ammAlice, env,
"dep2",
false);
6955 {{
GBP(30'000),
EUR(30'000)}},
6961 for (
auto const& exponent : {1, 2, 3, 4, -3 , -6, -9})
6964 [&](
AMM& ammAlice,
Env& env) {
6970 {
GBP(100'000),
EUR(100'000)},
6979 .account =
bob_, .asset1In = depositEuro, .asset2In = depositGBP});
6980 invariant(ammAlice, env,
"dep3", exponent != -3 && !env.
enabled(fixAMMv1_3));
6982 {{
GBP(10'000),
EUR(30'000)}},
6990 [&](
AMM& ammAlice,
Env& env) {
6996 invariant(ammAlice, env,
"dep4",
false);
6998 {{
GBP(7'000),
EUR(30'000)}},
7004 for (
auto const& tokens :
7015 [&](
AMM& ammAlice,
Env& env) {
7021 {
GBP(100'000),
EUR(1'000'000)},
7028 invariant(ammAlice, env,
"dep5",
false);
7030 {{
GBP(7'000),
EUR(30'000)}},
7039 [&](
AMM& ammAlice,
Env& env) {
7044 invariant(ammAlice, env,
"dep6",
false);
7046 {{
GBP(30'000),
EUR(30'000)}},
7057 using namespace jtx;
7061 [&](
AMM& ammAlice,
Env& env) {
7063 invariant(ammAlice, env,
"with1",
false);
7065 {{
GBP(7'000),
EUR(30'000)}},
7072 [&](
AMM& ammAlice,
Env& env) {
7074 invariant(ammAlice, env,
"with2",
false);
7076 {{
GBP(7'000),
EUR(30'000)}},
7083 [&](
AMM& ammAlice,
Env& env) {
7089 .flags = tfTwoAsset});
7090 invariant(ammAlice, env,
"with3",
false);
7092 {{
GBP(7'000),
EUR(30'000)}},
7103 [&](
AMM& ammAlice,
Env& env) {
7108 .flags = tfSingleAsset});
7109 invariant(ammAlice, env,
"with4",
false);
7111 {{
GBP(7'000),
EUR(30'000)}},
7118 [&](
AMM& ammAlice,
Env& env) {
7128 .flags = tfOneAssetWithdrawAll});
7129 invariant(ammAlice, env,
"with5",
false);
7131 {{
GBP(7'000),
EUR(30'000)}},
7138 [&](
AMM& ammAlice,
Env& env) {
7144 .flags = tfOneAssetLPToken});
7145 invariant(ammAlice, env,
"with6",
false);
7147 {{
GBP(7'000),
EUR(30'000)}},
7154 [&](
AMM& ammAlice,
Env& env) {
7160 .flags = tfLimitLPToken});
7161 invariant(ammAlice, env,
"with7",
true);
7163 {{
GBP(7'000),
EUR(30'000)}},
7172 testcase(
"Test AuthAccounts reset after empty pool reinitialization");
7173 using namespace jtx;
7200 .authAccounts = {bob_, dan},
7203 BEAST_EXPECT(
amm.expectAuctionSlot({bob_.id(), dan.id()}));
7206 amm.withdrawAll(alice_);
7207 amm.withdrawAll(carol_);
7208 BEAST_EXPECT(
amm.ammExists());
7211 BEAST_EXPECT(
amm.getLPTokensBalance() ==
IOUAmount{0});
7212 BEAST_EXPECT(
amm.expectAuctionSlot({bob_.id(), dan.id()}));
7226 BEAST_EXPECT(ammSle && ammSle->isFieldPresent(sfAuctionSlot));
7231 BEAST_EXPECT(
amm.expectAuctionSlot(50, 0,
IOUAmount{0}));
7234 BEAST_EXPECT(slot[sfAccount] == ed.id());
7236 BEAST_EXPECT(ammSle->getFieldU16(sfTradingFee) == 500);
7238 auto const& votes = ammSle->getFieldArray(sfVoteSlots);
7239 BEAST_EXPECT(votes.size() == 1);
7242 BEAST_EXPECT(votes[0].getAccountID(sfAccount) == ed.id());
7243 BEAST_EXPECT(votes[0].getFieldU16(sfTradingFee) == 500);
7247 if (features[fixCleanup3_2_0])
7249 BEAST_EXPECT(!slot.isFieldPresent(sfAuthAccounts));
7254 slot.isFieldPresent(sfAuthAccounts) && !slot.getFieldArray(sfAuthAccounts).empty());
7261 testcase(
"Deposit integral overflow");
7263 using namespace jtx;
7299 .asset1In = asset1In,
7301 .err =
Ter(expected)});
7317 testcase(
"Deposit EPrice integral overflow");
7319 using namespace jtx;
7353 .account =
alice_, .asset1In =
XRP(0), .maxEP = ePrice, .err =
Ter(expected)});
7378 testcase(
"Withdraw integral no overflow");
7380 using namespace jtx;
7399 Env env(*
this, features);
7415 .asset2Out =
XRP(1),
7420 test(all - featureMPTokensV2);
7440 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.
static constexpr int kMinOffset
Asset const & asset() const
static constexpr std::uint64_t kMinValue
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.
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.
bool enabled(uint256 feature) const
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)
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 expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
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
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Generate a pseudo-account address from a pseudo owner key.
Dest safeDowncast(Src *s) noexcept
bool isXRP(AccountID const &c)
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.
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
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()
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)
static std::vector< FeatureBitset > amendmentCombinations(std::initializer_list< uint256 > 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.