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/json/json_value.h>
29#include <xrpl/ledger/ApplyView.h>
30#include <xrpl/ledger/helpers/AMMHelpers.h>
31#include <xrpl/ledger/helpers/AccountRootHelpers.h>
32#include <xrpl/protocol/AMMCore.h>
33#include <xrpl/protocol/AccountID.h>
34#include <xrpl/protocol/AmountConversions.h>
35#include <xrpl/protocol/Feature.h>
36#include <xrpl/protocol/Indexes.h>
37#include <xrpl/protocol/Issue.h>
38#include <xrpl/protocol/LedgerFormats.h>
39#include <xrpl/protocol/Protocol.h>
40#include <xrpl/protocol/Quality.h>
41#include <xrpl/protocol/Rules.h>
42#include <xrpl/protocol/SField.h>
43#include <xrpl/protocol/STAmount.h>
44#include <xrpl/protocol/TER.h>
45#include <xrpl/protocol/TxFlags.h>
46#include <xrpl/protocol/UintTypes.h>
47#include <xrpl/protocol/jss.h>
48#include <xrpl/tx/Transactor.h>
49#include <xrpl/tx/transactors/dex/AMMBid.h>
51#include <boost/regex/v5/regex.hpp>
52#include <boost/regex/v5/regex_search.hpp>
106 [&](
AMM& ammAlice,
Env&) {
118 [&](
AMM& ammAlice,
Env&) {
129 [&](
AMM& ammAlice,
Env&) {
132 {{
USD(20'000),
BTC(0.5)}});
152 env(
fset(
gw_, asfRequireAuth));
170 env(
fset(
gw_, asfGlobalFreeze));
181 BEAST_EXPECT(
amm.expectTradingFee(1'000));
182 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
208 BEAST_EXPECT(!ammAlice.ammExists());
216 BEAST_EXPECT(!ammAlice.ammExists());
224 BEAST_EXPECT(!ammAlice.ammExists());
238 BEAST_EXPECT(!ammAlice.ammExists());
246 BEAST_EXPECT(!ammAlice.ammExists());
254 BEAST_EXPECT(!ammAlice.ammExists());
273 BEAST_EXPECT(!ammAlice.ammExists());
297 BEAST_EXPECT(!ammAlice.ammExists());
325 env(
fset(
gw_, asfRequireAuth));
338 env(
fset(
gw_, asfGlobalFreeze));
342 for (
auto const& account : {
alice_,
gw_})
345 BEAST_EXPECT(!
amm.ammExists());
367 auto const startingXrp =
XRP(1'000) +
reserve(env, 3) + env.
current()->fees().base * 4;
382 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 5;
419 AMM const ammAMMToken(
425 AMM const ammAMMToken1(
437 auto const token1 = ammAlice.
lptIssue();
438 auto const token2 = ammAlice1.lptIssue();
439 AMM const ammAMMTokens(
459 env(
fclear(gw1, asfDefaultRipple));
462 auto const usD1 = gw1[
"USD"];
490 {tfLPToken, 1'000, std::nullopt,
USD(100), std::nullopt, std::nullopt},
491 {tfLPToken, 1'000,
XRP(100), std::nullopt, std::nullopt, std::nullopt},
492 {tfLPToken, 1'000, std::nullopt, std::nullopt,
STAmount{
USD, 1, -1}, std::nullopt},
499 {tfLPToken, 1'000,
XRP(100), std::nullopt,
STAmount{
USD, 1, -1}, std::nullopt},
500 {tfLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, 1'000},
501 {tfSingleAsset, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
502 {tfSingleAsset, std::nullopt, std::nullopt,
USD(100), std::nullopt, std::nullopt},
509 {tfSingleAsset, std::nullopt,
USD(100), std::nullopt, std::nullopt, 1'000},
510 {tfTwoAsset, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
511 {tfTwoAsset, std::nullopt,
XRP(100),
USD(100),
STAmount{
USD, 1, -1}, std::nullopt},
512 {tfTwoAsset, std::nullopt,
XRP(100), std::nullopt, std::nullopt, std::nullopt},
513 {tfTwoAsset, std::nullopt,
XRP(100),
USD(100), std::nullopt, 1'000},
520 {tfOneAssetLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
521 {tfOneAssetLPToken, std::nullopt,
XRP(100),
USD(100), std::nullopt, std::nullopt},
528 {tfOneAssetLPToken, 1'000,
XRP(100), std::nullopt, std::nullopt, 1'000},
529 {tfLimitLPToken, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
530 {tfLimitLPToken, 1'000,
USD(100), std::nullopt, std::nullopt, std::nullopt},
531 {tfLimitLPToken, std::nullopt,
USD(100),
XRP(100), std::nullopt, std::nullopt},
532 {tfLimitLPToken, std::nullopt,
XRP(100), std::nullopt,
STAmount{
USD, 1, -1}, 1'000},
533 {tfTwoAssetIfEmpty, std::nullopt, std::nullopt, std::nullopt, std::nullopt, 1'000},
534 {tfTwoAssetIfEmpty, 1'000, std::nullopt, std::nullopt, std::nullopt, std::nullopt},
541 {tfTwoAssetIfEmpty | tfLPToken,
547 for (
auto const& it : invalidOptions)
565 jv[jss::Account] =
alice_.human();
566 jv[jss::TransactionType] = jss::AMMDeposit;
580 jv[jss::Account] =
alice_.human();
581 jv[jss::TransactionType] = jss::AMMDeposit;
585 jv[jss::Flags] = tfLPToken;
724 [&](
AMM& ammAlice,
Env& env) {
725 auto const enabledV13 = env.
current()->rules().enabled(fixAMMv1_3);
745 {features, features - fixAMMv1_3});
755 [&](
AMM& ammAlice,
Env& env) {
756 env(
fset(
gw_, asfGlobalFreeze));
757 auto const freezeBlocksAll =
758 features[featureAMMClawback] || features[fixCleanup3_3_0];
759 if (!freezeBlocksAll)
773 for (
auto const& account : {
carol_,
gw_})
783 account, 1'000'000, std::nullopt, std::nullopt,
Ter(
tecFROZEN));
795 [&](
AMM& ammAlice,
Env& env) {
798 auto const freezeBlocksAll =
799 features[featureAMMClawback] || features[fixCleanup3_3_0];
800 if (!freezeBlocksAll)
827 if (!features[fixCleanup3_3_0])
847 [&](
AMM& ammAlice,
Env& env) {
866 {{
USD(20'000),
BTC(0.5)}});
870 Env env(*
this, features);
872 env(
fset(
gw_, asfRequireAuth));
885 if (features[featureAMMClawback] || features[fixCleanup3_3_0])
957 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 4;
991 auto const startingXrp =
reserve(env, 4) + env.
current()->fees().base * 4;
1158 {.pool = {{
USD(1'000'000),
XRP(1'000'000)}}, .features = {features - fixAMMv1_3}});
1198 using namespace jtx;
1202 auto const baseFee = env.
current()->fees().base;
1215 for (
Number const& deltaLPTokens :
1216 {
Number{UINT64_C(100000'0000000009), -10},
Number{UINT64_C(100000'0000000001), -10}})
1221 IOUAmount const newLPTokens{deltaLPTokens};
1233 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1234 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1238 Number const fr = deltaLPTokens / 1e7;
1242 Number const deltaXRP = fr * 1e10;
1243 Number const deltaUSD = fr * 1e4;
1251 XRP(10'000) + depositXRP,
1252 USD(10'000) + depositUSD,
1367 STAmount{USD, UINT64_C(10'000'000001), -6},
1387 for (
auto const& feat : {all, all - fixAMMv1_3})
1389 Env env(*
this, feat);
1480 using namespace jtx;
1483 [&](
AMM& ammAlice,
Env& env) {
1485 .asset1Out =
XRP(100),
1493 [&](
AMM& ammAlice,
Env& env) {
1495 .asset1Out =
USD(100),
1506 env(
fset(
gw_, asfRequireAuth));
1519 .asset1Out =
USD(100),
1555 NotTEC>>
const invalidOptions = {
1567 tfSingleAsset | tfTwoAsset,
1569 {1'000, std::nullopt, std::nullopt, std::nullopt, tfWithdrawAll,
temMALFORMED},
1574 tfWithdrawAll | tfLPToken,
1576 {std::nullopt, std::nullopt,
USD(100), std::nullopt, tfWithdrawAll,
temMALFORMED},
1581 tfWithdrawAll | tfOneAssetWithdrawAll,
1583 {std::nullopt,
USD(100), std::nullopt, std::nullopt, tfWithdrawAll,
temMALFORMED},
1588 tfOneAssetWithdrawAll,
1590 {1'000, std::nullopt,
USD(100), std::nullopt, std::nullopt,
temMALFORMED},
1607 for (
auto const& it : invalidOptions)
1618 Ter(std::get<5>(it)));
1650 tfOneAssetWithdrawAll,
1699 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
1706 [&](
AMM& ammAlice,
Env& env) {
1716 alice_,
IOUAmount{9'999'999'9999, -4}, std::nullopt, std::nullopt, err);
1726 {all, all - fixAMMv1_3});
1729 [&](
AMM& ammAlice,
Env& env) {
1738 alice_,
IOUAmount{9'999'999'999999999, -9}, std::nullopt, std::nullopt, err);
1748 {all, all - fixAMMv1_3});
1758 [&](
AMM& ammAlice,
Env& env) {
1759 auto const fix330 = env.
current()->rules().enabled(fixCleanup3_3_0);
1760 ammAlice.
deposit({.account =
gw_, .asset1In =
USD(1'000), .asset2In =
XRP(1'000)});
1761 env(
fset(
gw_, asfGlobalFreeze));
1764 for (
auto const& account : {
alice_,
gw_})
1769 auto const frozenErr =
1771 ammAlice.
withdraw(account,
USD(100), std::nullopt, std::nullopt, frozenErr);
1772 ammAlice.
withdraw(account, 1'000, std::nullopt, std::nullopt, frozenErr);
1782 [&](
AMM& ammAlice,
Env& env) {
1783 auto const fix330 = env.
current()->rules().enabled(fixCleanup3_3_0);
1791 ammAlice.
withdraw(
alice_, 1'000, std::nullopt, std::nullopt, indivFreezeErr);
1792 ammAlice.
withdraw(
alice_,
USD(100), std::nullopt, std::nullopt, indivFreezeErr);
1826 [&](
AMM& ammAlice,
Env& env) {
1835 {all, all - fixAMMv1_3});
1855 [&](
AMM& ammAlice,
Env&) {
1863 [&](
AMM& ammAlice,
Env&) {
1885 [&](
AMM& ammAlice,
Env& env) {
1891 auto const err = [&] {
1894 if (env.
enabled(fixCleanup3_3_0))
1905 {.features = {all, all - fixAMMv1_3, all - fixCleanup3_3_0}, .noLog =
true});
1949 using namespace jtx;
1954 auto const baseFee = env.
current()->fees().base.drops();
1999 [&](
AMM& ammAlice,
Env& env) {
2015 {all, all - fixAMMv1_3});
2063 [&](
AMM& ammAlice,
Env& env) {
2088 {all, all - fixAMMv1_3});
2130 [&](
AMM& ammAlice,
Env& env) {
2148 else if (env.
enabled(fixAMMv1_3))
2158 {.features = {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3}, .noLog =
true});
2162 [&](
AMM& ammAlice,
Env& env) {
2180 else if (env.
enabled(fixAMMv1_3))
2191 {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3});
2224 STAmount{USD, UINT64_C(9'999'999999), -6},
2228 [&](
AMM& ammAlice,
Env& env) {
2245 {all, all - fixAMMv1_3});
2294 [&](
AMM& ammAlice,
Env& env) {
2300 .asset1Out =
USD(0),
2307 {all - fixCleanup3_3_0, all});
2314 using namespace jtx;
2328 std::nullopt, 1'001, std::nullopt, std::nullopt, std::nullopt,
Ter(
temBAD_FEE));
2361 using namespace jtx;
2366 ammAlice.
vote({}, 1'000);
2372 auto vote = [&](
AMM& ammAlice,
2375 int fundUSD = 100'000,
2385 fund(env,
gw_, {a}, {
USD(fundUSD)}, Fund::Acct);
2387 ammAlice.
vote(a, 50 * (i + 1));
2394 [&](
AMM& ammAlice,
Env& env) {
2395 for (
int i = 0; i < 7; ++i)
2396 vote(ammAlice, env, i, 10'000);
2407 for (
int i = 0; i < 7; ++i)
2408 vote(ammAlice, env, i);
2411 ammAlice.
vote(a, 450);
2418 for (
int i = 0; i < 7; ++i)
2419 vote(ammAlice, env, i);
2421 vote(ammAlice, env, 7, 100'000, 20'000'000);
2428 for (
int i = 7; i > 0; --i)
2429 vote(ammAlice, env, i);
2431 vote(ammAlice, env, 0, 100'000, 20'000'000);
2440 for (
int i = 0; i < 7; ++i)
2441 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2443 for (
int i = 0; i < 7; ++i)
2457 for (
int i = 0; i < 7; ++i)
2458 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2460 for (
int i = 0; i < 7; ++i)
2461 ammAlice.
withdraw(accounts[i], 9'000'000);
2465 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2467 BEAST_EXPECT(info[i][jss::account] !=
carol_.human());
2477 using namespace jtx;
2487 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2494 .bidMin = 1'000'000,
2506 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2513 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2519 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2525 BEAST_EXPECT(
Number{
amm.getLPTokensBalance(
gw_)} == 1);
2542 .flags = tfWithdrawAll,
2548 for (
auto bid : {0, -100})
2597 .authAccounts = {bob_},
2605 .assets = {{USD, GBP}},
2612 .bidMax = STAmount{USD, 100},
2614 Ter(temBAD_AMM_TOKENS));
2617 .bidMin = STAmount{USD, 100},
2619 Ter(temBAD_AMM_TOKENS));
2623 testAMM([&](AMM& ammAlice, Env& env) {
2624 ammAlice.withdrawAll(alice_);
2633 testAMM([&](AMM& ammAlice, Env& env) {
2638 env.fund(
XRP(1'000), bob_, ed, bill, scott, james);
2640 ammAlice.deposit(carol_, 1'000'000);
2644 .authAccounts = {bob_, ed, bill, scott, james},
2650 testAMM([&](AMM& ammAlice, Env& env) {
2651 fund(env, gw_, {bob_},
XRP(1'000), {USD(100)}, Fund::Acct);
2652 ammAlice.deposit(carol_, 1'000'000);
2653 ammAlice.deposit(bob_, 10);
2656 .bidMin = 1'000'001,
2658 Ter(tecAMM_INVALID_TOKENS));
2661 .bidMax = 1'000'001,
2663 Ter(tecAMM_INVALID_TOKENS));
2668 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2673 Ter(tecAMM_INVALID_TOKENS));
2679 fund(env, gw_, {alice_, bob_},
XRP(1'000), {USD(1'000)});
2680 AMM
amm(env, gw_,
XRP(10), USD(1'000));
2681 auto const lpIssue =
amm.lptIssue();
2682 env.trust(STAmount{lpIssue, 100}, alice_);
2683 env.trust(STAmount{lpIssue, 50}, bob_);
2684 env(
pay(gw_, alice_, STAmount{lpIssue, 100}));
2685 env(
pay(gw_, bob_, STAmount{lpIssue, 50}));
2686 env(
amm.bid({.account = alice_, .bidMin = 100}));
2693 Ter(tecAMM_FAILED));
2701 using namespace jtx;
2708 [&](
AMM& ammAlice,
Env& env) {
2710 env(ammAlice.
bid({.account = carol_, .bidMin = 110}));
2723 [&](
AMM& ammAlice,
Env& env) {
2726 env(ammAlice.
bid({.account = carol_, .bidMin = 110, .bidMax = 110}));
2731 env(ammAlice.
bid({.account = alice_, .bidMin = 180, .bidMax = 200}));
2743 [&](
AMM& ammAlice,
Env& env) {
2746 env(ammAlice.
bid({.account = carol_, .bidMin = 110}));
2752 env(ammAlice.
bid({.account = bob_}));
2762 env(ammAlice.
bid({.account = carol_, .bidMax = 600}));
2774 env(ammAlice.
bid({.account = carol_, .bidMin = 100, .bidMax = 600}));
2784 [&](
AMM& ammAlice,
Env& env) {
2789 if (!features[fixAMMv1_3])
2801 env(ammAlice.
bid({.account = carol_, .bidMin = 110})).
close();
2805 env(ammAlice.
bid({.account = bob_}));
2810 env(ammAlice.
bid({.account = carol_}));
2815 env(ammAlice.
bid({.account = bob_}));
2820 env(ammAlice.
bid({.account = carol_, .bidMin = 110})).
close();
2823 if (!features[fixAMMv1_3])
2844 [&](
AMM& ammAlice,
Env& env) {
2849 ammAlice.
deposit(ed, 1'000'000);
2851 ammAlice.
deposit(dan, 500'000);
2856 .authAccounts = {bob_, ed},
2858 auto const slotPrice =
IOUAmount{5'200};
2859 ammTokens -= slotPrice;
2861 if (!features[fixAMMv1_3])
2871 for (
int i = 0; i < 10; ++i)
2881 if (!features[fixAMMv1_1])
2885 STAmount(
USD, UINT64_C(29'499'00572620545), -11));
2892 XRP(13'000),
STAmount(
USD, UINT64_C(13'002'98282151419), -11), ammTokens));
2898 STAmount(
USD, UINT64_C(29'499'00572620544), -11));
2904 if (!features[fixAMMv1_3])
2921 for (
int i = 0; i < 10; ++i)
2923 auto const tokens = ammAlice.
deposit(dan,
USD(100));
2929 if (!features[fixAMMv1_1])
2935 XRP(13'000),
STAmount{
USD, UINT64_C(13'012'92609877019), -11}, ammTokens));
2940 XRP(13'000),
STAmount{
USD, UINT64_C(13'112'92609877019), -11}, ammTokens));
2947 STAmount{USD, UINT64_C(13'012'92609877019), -11},
2952 if (!features[fixAMMv1_3])
2956 STAmount(
USD, UINT64_C(19'490'05672274399), -11));
2962 STAmount(
USD, UINT64_C(19'490'05672274398), -11));
2965 if (!features[fixAMMv1_3])
2976 STAmount{USD, UINT64_C(13'012'92609877024), -11},
2982 if (!features[fixAMMv1_3])
2993 STAmount{USD, UINT64_C(13'112'92609877024), -11},
3000 if (!features[fixAMMv1_3])
3004 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3011 STAmount{USD, UINT64_C(13'012'92609877024), -11},
3021 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3025 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3028 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3032 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3039 STAmount{USD, UINT64_C(13'114'03663044937), -11},
3046 if (!features[fixAMMv1_1])
3050 STAmount(
USD, UINT64_C(29'399'00572620545), -11));
3052 else if (!features[fixAMMv1_3])
3056 STAmount(
USD, UINT64_C(29'399'00572620544), -11));
3059 for (
int i = 0; i < 10; ++i)
3066 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3070 STAmount(
USD, UINT64_C(29'389'06197177128), -11));
3073 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3076 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3080 STAmount(
USD, UINT64_C(29'389'06197177124), -11));
3083 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3090 STAmount(
USD, UINT64_C(29'389'06197177129), -11));
3093 STAmount{USD, UINT64_C(13'123'98038488352), -11},
3101 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3108 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3130 [&](AMM& ammAlice, Env& env) {
3133 env(ammAlice.bid({.account = alice_, .bidMin = IOUAmount{tiny}}));
3136 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
3138 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'000), USD(10'000), ammAlice.tokens()));
3142 .bidMin = IOUAmount{STAmount::kMinValue, STAmount::kMinOffset},
3145 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny * Number{105, -2}}));
3148 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'000), USD(10'000), ammAlice.tokens()));
3157 [&](AMM& ammAlice, Env& env) {
3160 .bidMin = IOUAmount{100},
3161 .authAccounts = {carol_},
3163 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol_}));
3164 env(ammAlice.bid({.account = alice_, .bidMin = IOUAmount{100}}));
3165 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3168 fund(env, {bob, dan},
XRP(1'000));
3171 .bidMin = IOUAmount{100},
3172 .authAccounts = {bob, dan},
3174 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3183 Env env(*
this, features);
3184 fund(env, gw_, {alice_, bob_},
XRP(2'000), {USD(2'000)});
3185 AMM
amm(env, gw_,
XRP(1'000), USD(1'010),
false, 1'000);
3186 auto const lpIssue =
amm.lptIssue();
3187 env.trust(STAmount{lpIssue, 500}, alice_);
3188 env.trust(STAmount{lpIssue, 50}, bob_);
3189 env(
pay(gw_, alice_, STAmount{lpIssue, 500}));
3190 env(
pay(gw_, bob_, STAmount{lpIssue, 50}));
3192 env(
amm.bid({.account = alice_, .bidMin = 500}));
3193 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3194 BEAST_EXPECT(
expectHolding(env, alice_, STAmount{lpIssue, 0}));
3197 env(
pay(alice_, bob_, USD(10)), Path(~USD), Sendmax(
XRP(11)));
3198 BEAST_EXPECT(
amm.expectBalances(
3199 XRPAmount{1'010'010'011}, USD(1'000), IOUAmount{1'004'487'562112089, -9}));
3201 env(
pay(bob_, alice_,
XRP(10)), Path(~XRP), Sendmax(USD(11)));
3202 if (!features[fixAMMv1_1])
3204 BEAST_EXPECT(
amm.expectBalances(
3205 XRPAmount{1'000'010'011},
3206 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3207 IOUAmount{1'004'487'562112089, -9}));
3211 BEAST_EXPECT(
amm.expectBalances(
3212 XRPAmount{1'000'010'011},
3213 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3214 IOUAmount{1'004'487'562112089, -9}));
3220 Env env(*
this, features);
3221 auto const baseFee = env.current()->fees().base;
3223 fund(env, gw_, {alice_, bob_},
XRP(2'000), {USD(2'000)});
3224 AMM
amm(env, gw_,
XRP(1'000), USD(1'010),
false, 1'000);
3225 json::Value const tx =
amm.bid({.account = alice_, .bidMin = 500});
3228 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3229 env.app().config().features.erase(featureAMM);
3230 PreflightContext
const pfCtx(
3231 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3232 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3233 BEAST_EXPECT(pf == temDISABLED);
3234 env.app().config().features.insert(featureAMM);
3238 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3239 jtx.jv[
"TxnSignature"] =
"deadbeef";
3240 jtx.stx = env.ust(jtx);
3241 PreflightContext
const pfCtx(
3242 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3243 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3248 auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
3249 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3250 jtx.jv[
"Asset2"].removeMember(
"issuer");
3251 jtx.stx = env.ust(jtx);
3252 PreflightContext
const pfCtx(
3253 env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
3254 auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
3255 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3264 using namespace jtx;
3276 AMM const ammAlice(env, acct,
XRP(10),
USD(10));
3288 AMM const ammAlice(env, acct,
XRP(1'000'000),
USD(100));
3298 auto const baseFee = env.
current()->fees().base;
3309 auto const pk =
carol_.pk();
3310 auto const settleDelay = 100s;
3325 [&](
AMM& ammAlice,
Env& env) {
3362 env(
fset(
gw_, asfGlobalFreeze));
3366 Txflags(tfPartialPayment | tfNoRippleDirect),
3371 Txflags(tfPartialPayment | tfNoRippleDirect),
3383 Txflags(tfPartialPayment | tfNoRippleDirect),
3388 Txflags(tfPartialPayment | tfNoRippleDirect),
3401 Txflags(tfNoRippleDirect | tfPartialPayment),
3410 using namespace jtx;
3415 [&](
AMM& ammAlice,
Env& env) {
3430 {{
XRP(10'000),
USD(10'100)}},
3437 [&](
AMM& ammAlice,
Env& env) {
3449 {{
XRP(10'000),
USD(10'100)}},
3457 [&](
AMM& ammAlice,
Env& env) {
3469 {{
XRP(10'000),
USD(10'100)}},
3476 [&](
AMM& ammAlice,
Env& env) {
3484 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
3499 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
3503 {{
XRP(10'000),
USD(10'010)}},
3510 [&](
AMM& ammAlice,
Env& env) {
3521 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
3530 {{
XRP(10'000),
USD(10'010)}},
3537 [&](
AMM& ammAlice,
Env& env) {
3546 {{
XRP(10'000),
USD(10'000)}},
3556 Env env(*
this, features);
3570 BEAST_EXPECT(ammEurXrp.expectBalances(
3573 ammEurXrp.tokens()));
3574 if (!features[fixAMMv1_1])
3576 BEAST_EXPECT(ammUsdEur.expectBalances(
3579 ammUsdEur.tokens()));
3582 Amounts const expectedAmounts = env.
closed()->rules().enabled(fixReducedOffersV2)
3591 BEAST_EXPECT(ammUsdEur.expectBalances(
3594 ammUsdEur.tokens()));
3597 Amounts const expectedAmounts = env.
closed()->rules().enabled(fixReducedOffersV2)
3617 [&](
AMM& ammAlice,
Env& env) {
3643 STAmount(
USD, UINT64_C(49'98750312422), -11)}}}));
3662 [&](
AMM& ammAlice,
Env& env) {
3669 if (!features[fixAMMv1_1])
3694 {{
XRP(10'000),
USD(10'100)}},
3703 Env env(*
this, features);
3718 [&](
AMM& ammAlice,
Env& env) {
3731 {{
XRP(10'000),
USD(10'100)}},
3739 [&](
AMM& ammAlice,
Env& env) {
3756 {{
GBP(1'000),
EUR(1'100)}},
3767 if (!features[fixAMMv1_1])
3776 BEAST_EXPECT(
amm.expectBalances(
XRP(1'000),
USD(500),
amm.tokens()));
3788 BEAST_EXPECT(
amm.expectBalances(
3801 STAmount(
USD, UINT64_C(29'949'94999999494), -11));
3804 {{
XRP(1'000),
USD(500)}},
3814 if (!features[fixAMMv1_1])
3816 BEAST_EXPECT(
amm.expectBalances(
3822 BEAST_EXPECT(
amm.expectBalances(
3827 {{
XRP(1'000),
USD(500)}},
3833 [&](
AMM& ammAlice,
Env& env) {
3849 if (!features[fixAMMv1_1])
3858 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
3859 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
3868 STAmount{
GBP, UINT64_C(50'684828792831), -12}}}));
3893 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
3894 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
3903 STAmount{
GBP, UINT64_C(27'06583722134028), -14}}}));
3923 {{
GBP(1'000),
EUR(1'100)}},
3936 [&](
AMM& ammAlice,
Env& env) {
3949 {{
GBP(1'000),
EUR(1'100)}},
3966 [&](
AMM& ammAlice,
Env& env) {
3969 auto const can =
gw_[
"CAN"];
3970 fund(env,
gw_, {dan}, {can(200),
GBP(200)}, Fund::Acct);
3972 fund(env,
gw_, {
bob_}, {can(195.3125)}, Fund::Acct);
3976 env(
offer(dan, can(200),
GBP(200)));
3990 {{
GBP(10'000),
EUR(10'125)}},
3997 [&](
AMM& ammAlice,
Env& env) {
4026 Env env(*
this, features);
4027 auto const eth =
gw_[
"ETH"];
4033 {
EUR(50'000),
BTC(50'000), eth(50'000),
USD(50'000)});
4039 AMM const xrpEth(env,
alice_,
XRP(10'000), eth(10'100));
4040 AMM const ethEur(env,
alice_, eth(10'900),
EUR(11'000));
4047 if (!features[fixAMMv1_1])
4053 STAmount{eth, UINT64_C(10'073'65779244494), -11},
4056 STAmount{eth, UINT64_C(10'926'34220755506), -11},
4057 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4060 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4061 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4074 STAmount{eth, UINT64_C(10'073'65779244461), -11},
4077 STAmount{eth, UINT64_C(10'926'34220755539), -11},
4078 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4081 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4082 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4098 BEAST_EXPECT(xrpEur.expectBalances(
XRP(10'100),
EUR(10'000), xrpEur.tokens()));
4107 Env env(*
this, features);
4108 auto const eth =
gw_[
"ETH"];
4114 {
EUR(50'000),
BTC(50'000), eth(50'000),
USD(50'000)});
4119 AMM const xrpEth(env,
alice_,
XRP(10'000), eth(10'100));
4120 AMM const ethEur(env,
alice_, eth(10'900),
EUR(11'000));
4125 if (!features[fixAMMv1_1])
4129 BEAST_EXPECT(xrpEur.expectBalances(
4134 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4135 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4138 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
USD(9'900), btcUsd.
tokens()));
4141 STAmount{eth, UINT64_C(10'017'41072778012), -11},
4144 STAmount{eth, UINT64_C(10'982'58927221988), -11},
4145 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4150 BEAST_EXPECT(xrpEur.expectBalances(
4155 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4156 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4159 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
USD(9'900), btcUsd.
tokens()));
4162 STAmount{eth, UINT64_C(10'017'41072777996), -11},
4165 STAmount{eth, UINT64_C(10'982'58927222004), -11},
4166 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4175 [&](
AMM& ammAlice,
Env& env) {
4179 for (
int i = 0; i < 30; ++i)
4187 Txflags(tfPartialPayment | tfNoRippleDirect));
4188 if (!features[fixAMMv1_1])
4215 [&](
AMM& ammAlice,
Env& env) {
4219 for (
int i = 0; i < 29; ++i)
4227 Txflags(tfPartialPayment | tfNoRippleDirect));
4230 if (!features[fixAMMv1_1])
4254 Env env(*
this, features);
4259 if (!features[fixAMMv1_1])
4263 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4275 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4288 [&](
AMM& ammAlice,
Env& env) {
4295 Txflags(tfNoRippleDirect | tfPartialPayment),
4308 using namespace jtx;
4312 auto const baseFee = env.
current()->fees().base.drops();
4313 auto const token1 = ammAlice.
lptIssue();
4334 env(ammAlice.
bid({.account = carol_, .bidMin = 100}));
4359 ammAlice1.deposit(
carol_, 1'000'000);
4360 auto const token1 = ammAlice.
lptIssue();
4361 auto const token2 = ammAlice1.lptIssue();
4380 auto const token1 = ammAlice.
lptIssue();
4412 using namespace jtx;
4433 using namespace jtx;
4436 auto const info = env.
rpc(
4440 auto const flags = info[jss::result][jss::account_data][jss::Flags].
asUInt();
4441 BEAST_EXPECT(flags == (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
4449 using namespace jtx;
4465 auto const tsta = a[
"TST"];
4466 auto const tstb = b[
"TST"];
4475 env.
trust(tsta(10'000), c);
4476 env.
trust(tstb(10'000), c);
4477 env(
pay(a, c, tsta(10'000)));
4478 env(
pay(b, c, tstb(10'000)));
4479 AMM const amm(env, c, tsta(5'000), tstb(5'000));
4480 auto const ammIss =
Issue(tsta.currency,
amm.ammAccount());
4490 Txflags(tfPartialPayment | tfNoRippleDirect),
4498 testcase(
"AMMAndCLOB, offer quality change");
4499 using namespace jtx;
4500 auto const gw =
Account(
"gw");
4501 auto const tst = gw[
"TST"];
4502 auto const lP1 =
Account(
"LP1");
4503 auto const lP2 =
Account(
"LP2");
4505 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4506 Env env(*
this, features);
4507 env.
fund(
XRP(30'000'000'000), gw);
4508 env(
offer(gw,
XRP(11'500'000'000), tst(1'000'000'000)));
4529 [&](
Env& env) {
AMM const amm(env, lP1, tst(25),
XRP(250)); },
4531 lp2TSTBalance =
getAccountLines(env, lP2, tst)[
"lines"][0u][
"balance"].asString();
4533 lp2TakerGets =
offer[
"taker_gets"].asString();
4534 lp2TakerPays =
offer[
"taker_pays"][
"value"].asString();
4539 if (!features[fixAMMv1_1])
4544 STAmount{tst, UINT64_C(1'68737984885388), -14}),
4552 STAmount{tst, UINT64_C(1'68737976189735), -14}),
4561 BEAST_EXPECT(lp2TakerGets ==
offer[
"taker_gets"].asString());
4562 BEAST_EXPECT(lp2TakerPays ==
offer[
"taker_pays"][
"value"].asString());
4570 using namespace jtx;
4574 [&](
AMM& ammAlice,
Env& env) {
4596 {{
USD(1'000),
EUR(1'000)}},
4604 [&](
AMM& ammAlice,
Env& env) {
4615 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4616 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4626 [&](
AMM& ammAlice,
Env& env) {
4628 auto const tokensFee =
4637 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4638 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4647 [&](
AMM& ammAlice,
Env& env) {
4661 {{
USD(1'000),
EUR(1'000)}},
4668 [&](
AMM& ammAlice,
Env& env) {
4670 auto const tokensFee =
4673 auto const balanceAfterWithdraw = [&]() {
4674 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4676 return STAmount(
USD, UINT64_C(30'443'43891402715), -11);
4678 if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4680 return STAmount(
USD, UINT64_C(30'443'43891402714), -11);
4683 return STAmount(
USD, UINT64_C(30'443'43891402713), -11);
4687 auto const deposit = balanceAfterWithdraw -
USD(29'000);
4693 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4697 STAmount(
USD, UINT64_C(30'443'43891402717), -11));
4699 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4703 STAmount(
USD, UINT64_C(30'443'43891402716), -11));
4709 STAmount(
USD, UINT64_C(30'443'43891402713), -11));
4713 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4715 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4717 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
4719 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4723 BEAST_EXPECT(tokensNoFee ==
IOUAmount(746'579'80779911, -8));
4725 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4734 [&](
AMM& ammAlice,
Env& env) {
4770 {{
USD(1'000),
EUR(1'010)}},
4777 [&](
AMM& ammAlice,
Env& env) {
4803 STAmount{
USD, UINT64_C(5'025125628140703), -15}}}}));
4804 if (!features[fixAMMv1_1])
4807 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4808 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4814 STAmount{USD, UINT64_C(1'004'97487437186), -11},
4815 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4819 {{
USD(1'000),
EUR(1'010)}},
4829 Env env(*
this, features);
4836 if (!features[fixAMMv1_1])
4856 Env env(*
this, features);
4864 if (!features[fixAMMv1_1])
4889 Env env(*
this, features);
4894 AMM const ammAlice(env,
alice_,
USD(1'005),
EUR(1'000),
false, 1'000);
4907 Env env(*
this, features);
4912 AMM const ammAlice(env,
alice_,
USD(1'005),
EUR(1'000),
false, 1'000);
4926 testcase(
"Adjusted Deposit/Withdraw Tokens");
4928 using namespace jtx;
4932 [&](
AMM& ammAlice,
Env& env) {
4940 Account const natalie(
"natalie");
4944 {bob, ed, paul, dan, chris, simon, ben, natalie},
4947 for (
int i = 0; i < 10; ++i)
4971 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4978 else if (features[fixAMMv1_3])
4994 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
4999 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5009 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5012 env, natalie,
STAmount{
USD, UINT64_C(1'500'000'000000002), -9}));
5014 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5017 env, natalie,
STAmount{
USD, UINT64_C(1'500'000'000000005), -9}));
5025 if (!features[fixAMMv1_1])
5030 else if (features[fixAMMv1_3])
5052 [&](
AMM& ammAlice,
Env& env) {
5060 Account const natalie(
"natalie");
5064 {bob, ed, paul, dan, chris, simon, ben, natalie},
5068 for (
int i = 0; i < 10; ++i)
5089 auto const baseFee = env.
current()->fees().base.drops();
5090 if (!features[fixAMMv1_3])
5098 auto const xrpBalance = (
XRP(2'000'000) -
txFee(env, 20)).getText();
5123 auto const xrpBalance =
XRP(2'000'000) -
txFee(env, 20) -
drops(10);
5124 auto const xrpBalanceText = xrpBalance.getText();
5151 using namespace jtx;
5174 BEAST_EXPECT(
amm.ammExists());
5184 std::nullopt, 100, std::nullopt, std::nullopt, std::nullopt,
Ter(
tecAMM_EMPTY));
5201 BEAST_EXPECT(
amm.expectBalances(
XRP(10'000),
USD(10'000),
amm.tokens()));
5202 BEAST_EXPECT(
amm.expectTradingFee(1'000));
5203 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5208 BEAST_EXPECT(!
amm.ammExists());
5231 BEAST_EXPECT(
amm.ammExists());
5235 BEAST_EXPECT(
amm.ammExists());
5238 BEAST_EXPECT(!
amm.ammExists());
5250 using namespace jtx;
5262 using namespace jtx;
5264 amm.setClose(
false);
5265 auto const info = env.
rpc(
5272 info[jss::result][jss::account_data][jss::AMMID].asString() ==
5285 for (
auto const& node : affected)
5287 if (node.isMember(sfModifiedNode.fieldName) &&
5288 node[sfModifiedNode.fieldName][sfLedgerEntryType.fieldName].asString() ==
5290 node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::Account]
5293 found = node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::AMMID]
5298 BEAST_EXPECT(found);
5310 testcase(
"Offer/Strand Selection");
5311 using namespace jtx;
5314 auto const eth = gw1[
"ETH"];
5315 auto const can = gw1[
"CAN"];
5321 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5326 env(
rate(gw1, gw1Rate));
5344 for (
auto i = 0; i < 3; ++i)
5346 Env env(*
this, features);
5347 prep(env, rates.first, rates.second);
5349 if (i == 0 || i == 2)
5355 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5361 BEAST_EXPECT(
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5370 BEAST_EXPECT(q[0] > q[1]);
5372 BEAST_EXPECT(q[0] == q[2]);
5379 for (
auto i = 0; i < 3; ++i)
5381 Env env(*
this, features);
5382 prep(env, rates.first, rates.second);
5384 if (i == 0 || i == 2)
5390 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5396 BEAST_EXPECT(
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5398 if (i == 0 || i == 2)
5417 for (
auto i = 0; i < 3; ++i)
5419 Env env(*
this, features);
5420 prep(env, rates.first, rates.second);
5422 if (i == 0 || i == 2)
5428 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5434 BEAST_EXPECT(!
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5436 if (i == 2 && !features[fixAMMv1_1])
5438 if (rates.first == 1.5)
5440 if (!features[fixAMMv1_1])
5447 STAmount{eth, UINT64_C(378'6327949540823), -13},
5448 STAmount{
USD, UINT64_C(283'9745962155617), -13}}}}));
5457 STAmount{eth, UINT64_C(378'6327949540813), -13},
5458 STAmount{
USD, UINT64_C(283'974596215561), -12}}}}));
5463 if (!features[fixAMMv1_1])
5470 STAmount{eth, UINT64_C(325'299461620749), -12},
5471 STAmount{
USD, UINT64_C(243'9745962155617), -13}}}}));
5480 STAmount{eth, UINT64_C(325'299461620748), -12},
5481 STAmount{
USD, UINT64_C(243'974596215561), -12}}}}));
5487 if (rates.first == 1.5)
5494 STAmount{eth, UINT64_C(378'6327949540812), -13},
5495 STAmount{
USD, UINT64_C(283'9745962155609), -13}}}}));
5504 STAmount{eth, UINT64_C(325'2994616207479), -13},
5505 STAmount{
USD, UINT64_C(243'9745962155609), -13}}}}));
5515 BEAST_EXPECT(q[1] > q[0]);
5517 BEAST_EXPECT(q[2] > q[1]);
5521 for (
auto i = 0; i < 3; ++i)
5523 Env env(*
this, features);
5524 prep(env, rates.first, rates.second);
5526 if (i == 0 || i == 2)
5532 amm.emplace(env, ed,
USD(1'000), eth(1'000));
5538 BEAST_EXPECT(!
amm->expectBalances(
USD(1'000), eth(1'000),
amm->tokens()));
5544 if (rates.first == 1.5)
5546 if (!features[fixAMMv1_1])
5555 STAmount{eth, UINT64_C(64'91106406735152), -14},
5568 STAmount{eth, UINT64_C(335'0889359326475), -13},
5576 if (!features[fixAMMv1_1])
5584 STAmount{eth, UINT64_C(335'0889359326485), -13},
5597 STAmount{eth, UINT64_C(335'0889359326475), -13},
5624 for (
auto i = 0; i < 3; ++i)
5626 Env env(*
this, features);
5627 prep(env, rates.first, rates.second);
5630 if (i == 0 || i == 2)
5632 env(
offer(ed, eth(400), can(400)),
Txflags(tfPassive));
5638 amm.emplace(env, ed, eth(1'000),
USD(1'000));
5648 if (i == 2 && !features[fixAMMv1_1])
5650 if (rates.first == 1.5)
5653 BEAST_EXPECT(
amm->expectBalances(
5654 STAmount{eth, UINT64_C(1'176'66038955758), -11},
5660 BEAST_EXPECT(
amm->expectBalances(
5661 STAmount{eth, UINT64_C(1'179'540094339627), -12},
5662 STAmount{USD, UINT64_C(847'7880529867501), -13},
5669 STAmount{eth, UINT64_C(343'3179205198749), -13},
5670 STAmount{can, UINT64_C(343'3179205198749), -13},
5673 STAmount{can, UINT64_C(362'2119470132499), -13},
5680 if (rates.first == 1.5)
5683 BEAST_EXPECT(
amm->expectBalances(
5684 STAmount{eth, UINT64_C(1'176'660389557593), -12},
5690 BEAST_EXPECT(
amm->expectBalances(
5691 STAmount{eth, UINT64_C(1'179'54009433964), -11},
5692 STAmount{USD, UINT64_C(847'7880529867501), -13},
5699 STAmount{eth, UINT64_C(343'3179205198749), -13},
5700 STAmount{can, UINT64_C(343'3179205198749), -13},
5703 STAmount{can, UINT64_C(362'2119470132499), -13},
5713 BEAST_EXPECT(q[1] > q[0]);
5714 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5722 testcase(
"Fix Default Inner Object");
5723 using namespace jtx;
5734 Env env(*
this, features);
5736 AMM amm(env,
gw_,
XRP(10),
USD(10), {.tfee = tfee, .close = closeLedger});
5761 all - fixInnerObjTemplate,
5787 testcase(
"Fix changeSpotPriceQuality");
5788 using namespace jtx;
5793 SucceedShouldSucceedResize,
5810 {
"0.001519763260828713",
"1558701",
Quality{5414253689393440221}, 1000, FailShouldSucceed},
5811 {
"0.01099814367603737",
"1892611",
Quality{5482264816516900274}, 1000, FailShouldSucceed},
5812 {
"0.78",
"796599",
Quality{5630392334958379008}, 1000, FailShouldSucceed},
5813 {
"105439.2955578965",
"49398693",
Quality{5910869983721805038}, 400, FailShouldSucceed},
5814 {
"12408293.23445213",
"4340810521",
Quality{5911611095910090752}, 997, FailShouldSucceed},
5815 {
"1892611",
"0.01099814367603737",
Quality{6703103457950430139}, 1000, FailShouldSucceed},
5816 {
"423028.8508101858",
"3392804520",
Quality{5837920340654162816}, 600, FailShouldSucceed},
5817 {
"44565388.41001027",
"73890647",
Quality{6058976634606450001}, 1000, FailShouldSucceed},
5818 {
"66831.68494832662",
"16",
Quality{6346111134641742975}, 0, FailShouldSucceed},
5819 {
"675.9287302203422",
"1242632304",
Quality{5625960929244093294}, 300, FailShouldSucceed},
5820 {
"7047.112186735699",
"1649845866",
Quality{5696855348026306945}, 504, FailShouldSucceed},
5821 {
"840236.4402981238",
"47419053",
Quality{5982561601648018688}, 499, FailShouldSucceed},
5822 {
"992715.618909774",
"189445631733",
Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
5823 {
"504636667521",
"185545883.9506651",
Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
5824 {
"992706.7218636649",
"189447316000",
Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
5825 {
"1.068737911388205",
"127860278877",
Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
5826 {
"17932506.56880419",
"189308.6043676173",
Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
5827 {
"1.066379294658174",
"128042251493",
Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
5828 {
"350131413924",
"1576879.110907892",
Quality{6487411636539049449}, 650,
Fail},
5829 {
"422093460",
"2.731797662057464",
Quality{6702911108534394924}, 1000,
Fail},
5830 {
"76128132223",
"367172.7148422662",
Quality{6487263463413514240}, 548,
Fail},
5831 {
"132701839250",
"280703770.7695443",
Quality{6273750681188885075}, 562,
Fail},
5832 {
"994165.7604612011",
"189551302411",
Quality{5697835592690668727}, 815,
Fail},
5833 {
"45053.33303227917",
"86612695359",
Quality{5625695218943638190}, 500,
Fail},
5834 {
"199649.077043865",
"14017933007",
Quality{5766034667318524880}, 324,
Fail},
5835 {
"27751824831.70903",
"78896950",
Quality{6272538159621630432}, 500,
Fail},
5836 {
"225.3731275781907",
"156431793648",
Quality{5477818047604078924}, 989,
Fail},
5837 {
"199649.077043865",
"14017933007",
Quality{5766036094462806309}, 324,
Fail},
5838 {
"3.590272027140361",
"20677643641",
Quality{5406056147042156356}, 808,
Fail},
5839 {
"1.070884664490231",
"127604712776",
Quality{5268620608623825741}, 293,
Fail},
5840 {
"3272.448829820197",
"6275124076",
Quality{5625710328924117902}, 81,
Fail},
5841 {
"0.009059512633902926",
"7994028",
Quality{5477511954775533172}, 1000,
Fail},
5844 {
"10",
"10.0",
Quality{xrpIouAmounts10100}, 100,
Fail},
5845 {
"10.0",
"10",
Quality{iouXrpAmounts10100}, 100,
Fail},
5846 {
"69864389131",
"287631.4543025075",
Quality{6487623473313516078}, 451, Succeed},
5847 {
"4328342973",
"12453825.99247381",
Quality{6272522264364865181}, 997, Succeed},
5848 {
"32347017",
"7003.93031579449",
Quality{6347261126087916670}, 1000, Succeed},
5849 {
"61697206161",
"36631.4583206413",
Quality{6558965195382476659}, 500, Succeed},
5850 {
"1654524979",
"7028.659825511603",
Quality{6487551345110052981}, 504, Succeed},
5851 {
"88621.22277293179",
"5128418948",
Quality{5766347291552869205}, 380, Succeed},
5852 {
"1892611",
"0.01099814367603737",
Quality{6703102780512015436}, 1000, Succeed},
5853 {
"4542.639373338766",
"24554809",
Quality{5838994982188783710}, 0, Succeed},
5854 {
"5132932546",
"88542.99750172683",
Quality{6419203342950054537}, 380, Succeed},
5855 {
"78929964.1549083",
"1506494795",
Quality{5986890029845558688}, 589, Succeed},
5856 {
"10096561906",
"44727.72453735605",
Quality{6487455290284644551}, 250, Succeed},
5857 {
"5092.219565514988",
"8768257694",
Quality{5626349534958379008}, 503, Succeed},
5858 {
"1819778294",
"8305.084302902864",
Quality{6487429398998540860}, 415, Succeed},
5859 {
"6970462.633911943",
"57359281",
Quality{6054087899185946624}, 850, Succeed},
5860 {
"3983448845",
"2347.543644281467",
Quality{6558965195382476659}, 856, Succeed},
5864 {
"771493171",
"1.243473020567508",
Quality{6707566798038544272}, 100, SucceedShouldFail},
5868 boost::regex
const rx(
"^\\d+$");
5869 boost::smatch match;
5873 auto rules = env.
current()->rules();
5877 for (
auto const& t :
tests)
5884 auto const& quality = std::get<Quality>(t);
5885 auto const tfee = std::get<std::uint16_t>(t);
5886 auto const status = std::get<Status>(t);
5887 auto const poolInIsXRP = boost::regex_search(std::get<0>(t), match, rx);
5888 auto const poolOutIsXRP = boost::regex_search(std::get<1>(t), match, rx);
5889 assert(!(poolInIsXRP && poolOutIsXRP));
5890 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
5891 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
5898 if (status == SucceedShouldSucceedResize)
5900 if (!features[fixAMMv1_1])
5902 BEAST_EXPECT(
Quality{*amounts} < quality);
5906 BEAST_EXPECT(
Quality{*amounts} >= quality);
5909 else if (status == Succeed)
5911 if (!features[fixAMMv1_1])
5914 Quality{*amounts} >= quality ||
5919 BEAST_EXPECT(
Quality{*amounts} >= quality);
5922 else if (status == FailShouldSucceed)
5924 BEAST_EXPECT(features[fixAMMv1_1] &&
Quality{*amounts} >= quality);
5926 else if (status == SucceedShouldFail)
5929 !features[fixAMMv1_1] &&
Quality{*amounts} < quality &&
5941 auto tinyOffer = [&]() {
5956 auto const takerPays =
5961 BEAST_EXPECT(
Quality(tinyOffer) < quality);
5963 else if (status == FailShouldSucceed)
5965 BEAST_EXPECT(!features[fixAMMv1_1]);
5967 else if (status == SucceedShouldFail)
5969 BEAST_EXPECT(features[fixAMMv1_1]);
5975 BEAST_EXPECT(!
strcmp(e.
what(),
"changeSpotPriceQuality failed"));
5976 BEAST_EXPECT(!features[fixAMMv1_1] && status == FailShouldSucceed);
5984 BEAST_EXPECT(!res.has_value());
5991 using namespace jtx;
5995 .flags = tfSingleAsset,
6003 .flags = tfOneAssetLPToken,
6011 .flags = tfLimitLPToken,
6019 .asset1Out =
XRP(100),
6020 .asset2Out =
XRP(100),
6028 .asset1Out =
XRP(100),
6029 .asset2Out =
BAD(100),
6037 jv[jss::TransactionType] = jss::AMMWithdraw;
6038 jv[jss::Flags] = tfLimitLPToken;
6039 jv[jss::Account] =
alice_.human();
6041 XRP(100).value().setJson(jv[jss::Amount]);
6042 USD(100).value().setJson(jv[jss::EPrice]);
6051 using namespace jtx;
6057 Account const gatehub{
"gatehub"};
6058 Account const bitstamp{
"bitstamp"};
6059 Account const trader{
"trader"};
6060 auto const usdGH = gatehub[
"USD"];
6061 auto const btcGH = gatehub[
"BTC"];
6062 auto const usdBIT = bitstamp[
"USD"];
6066 char const* testCase;
6067 double const poolUsdBIT;
6068 double const poolUsdGH;
6081 double const offer1BtcGH = 0.1;
6082 double const offer2BtcGH = 0.1;
6083 double const offer2UsdGH = 1;
6084 double const rateBIT = 0.0;
6085 double const rateGH = 0.0;
6090 for (
auto const& input : {
6092 .testCase =
"Test Fix Overflow Offer",
6095 .sendMaxUsdBIT{usdBIT(50)},
6096 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6099 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6100 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6101 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6102 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6103 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6104 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6105 .lpTokenBalance = {28'61817604250837, -14},
6106 .lpTokenBalanceAlt =
IOUAmount{28'61817604250836, -14},
6114 .testCase =
"Overflow test {1, 100, 0.111}",
6117 .sendMaxUsdBIT{usdBIT(0.111)},
6118 .sendUsdGH{usdGH, 100},
6121 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6122 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6123 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6124 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6125 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6126 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6127 .lpTokenBalance{10, 0},
6128 .offer1BtcGH = 1e-5,
6130 .offer2UsdGH = 1e-5,
6135 .testCase =
"Overflow test {1, 100, 1.00}",
6138 .sendMaxUsdBIT{usdBIT(1.00)},
6139 .sendUsdGH{usdGH, 100},
6142 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6143 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6144 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6145 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6146 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6147 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6148 .lpTokenBalance{10, 0},
6149 .offer1BtcGH = 1e-5,
6151 .offer2UsdGH = 1e-5,
6156 .testCase =
"Overflow test {1, 100, 4.6432}",
6159 .sendMaxUsdBIT{usdBIT(4.6432)},
6160 .sendUsdGH{usdGH, 100},
6163 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6164 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6165 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6166 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6167 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6168 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6169 .lpTokenBalance{10, 0},
6170 .offer1BtcGH = 1e-5,
6172 .offer2UsdGH = 1e-5,
6177 .testCase =
"Overflow test {1, 100, 10}",
6180 .sendMaxUsdBIT{usdBIT(10)},
6181 .sendUsdGH{usdGH, 100},
6184 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6185 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6186 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6187 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6188 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6189 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6190 .lpTokenBalance{10, 0},
6191 .offer1BtcGH = 1e-5,
6193 .offer2UsdGH = 1e-5,
6198 .testCase =
"Overflow test {50, 100, 5.55}",
6201 .sendMaxUsdBIT{usdBIT(5.55)},
6202 .sendUsdGH{usdGH, 100},
6205 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6206 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6207 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6208 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6209 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6210 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6211 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6212 .offer1BtcGH = 1e-5,
6214 .offer2UsdGH = 1e-5,
6219 .testCase =
"Overflow test {50, 100, 50.00}",
6222 .sendMaxUsdBIT{usdBIT(50.00)},
6223 .sendUsdGH{usdGH, 100},
6224 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6225 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6226 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6227 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6228 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6229 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6230 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6231 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6232 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6233 .offer1BtcGH = 1e-5,
6235 .offer2UsdGH = 1e-5,
6240 .testCase =
"Overflow test {50, 100, 232.16}",
6243 .sendMaxUsdBIT{usdBIT(232.16)},
6244 .sendUsdGH{usdGH, 100},
6247 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6248 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6249 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6250 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6251 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6252 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6253 .lpTokenBalance{70'71067811865475, -14},
6254 .offer1BtcGH = 1e-5,
6256 .offer2UsdGH = 1e-5,
6261 .testCase =
"Overflow test {50, 100, 500}",
6264 .sendMaxUsdBIT{usdBIT(500)},
6265 .sendUsdGH{usdGH, 100},
6268 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6269 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6270 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6271 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6272 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6273 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6274 .lpTokenBalance{70'71067811865475, -14},
6275 .offer1BtcGH = 1e-5,
6277 .offer2UsdGH = 1e-5,
6284 for (
auto const& features : {all - fixAMMOverflowOffer - fixAMMv1_1 - fixAMMv1_3, all})
6288 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6291 if (input.rateGH != 0.0)
6292 env(
rate(gatehub, input.rateGH));
6293 if (input.rateBIT != 0.0)
6294 env(
rate(bitstamp, input.rateBIT));
6296 env(
trust(trader, usdGH(10'000'000)));
6297 env(
trust(trader, usdBIT(10'000'000)));
6298 env(
trust(trader, btcGH(10'000'000)));
6301 env(
pay(gatehub, trader, usdGH(100'000)));
6302 env(
pay(gatehub, trader, btcGH(100'000)));
6303 env(
pay(bitstamp, trader, usdBIT(100'000)));
6306 AMM const amm{env, trader, usdGH(input.poolUsdGH), usdBIT(input.poolUsdBIT)};
6309 IOUAmount const preSwapLPTokenBalance =
amm.getLPTokensBalance();
6311 env(
offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6312 env(
offer(trader, btcGH(input.offer2BtcGH), usdGH(input.offer2UsdGH)));
6315 env(
pay(trader, trader, input.sendUsdGH),
6317 Path(~btcGH, ~usdGH),
6322 auto const failUsdGH = features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6323 auto const failUsdBIT = features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6324 auto const goodUsdGH = features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6325 auto const goodUsdBIT = features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6326 auto const lpTokenBalance = [&] {
6327 if (not env.
enabled(fixAMMv1_3))
6328 return input.lpTokenBalance;
6330 return input.lpTokenBalanceAlt.value_or(input.lpTokenBalance);
6333 if (!features[fixAMMOverflowOffer])
6335 BEAST_EXPECT(
amm.expectBalances(failUsdGH, failUsdBIT, lpTokenBalance));
6339 BEAST_EXPECT(
amm.expectBalances(goodUsdGH, goodUsdBIT, lpTokenBalance));
6343 BEAST_EXPECT(
amm.getLPTokensBalance() == preSwapLPTokenBalance);
6347 Number const sqrtPoolProduct =
root2(goodUsdGH * goodUsdBIT);
6357 BEAST_EXPECT((sqrtPoolProduct +
Number{1, -14} >= input.lpTokenBalance));
6367 using namespace jtx;
6369 STAmount const xrpPool{
XRP, UINT64_C(51600'000981)};
6370 STAmount const iouPool{
USD, UINT64_C(803040'9987141784), -10};
6372 STAmount const xrpBob{
XRP, UINT64_C(1092'878933)};
6373 STAmount const iouBob{
USD, UINT64_C(3'988035892323031), -28};
6378 auto [xrpBegin, iouBegin, lptBegin] =
amm.balances(
XRP,
USD);
6390 BEAST_EXPECT(
amm.expectBalances(xrpBegin, iouBegin,
amm.tokens()));
6392 {{xrpPool, iouPool}},
6401 testcase(
"AMM Offer Blocked By LOB");
6402 using namespace jtx;
6408 Env env(*
this, features);
6422 if (!features[fixAMMv1_1])
6424 BEAST_EXPECT(
amm.expectBalances(
XRP(200'000),
USD(100'000),
amm.tokens()));
6442 Env env(*
this, features);
6463 Env env(*
this, features);
6473 if (!features[fixAMMv1_1])
6475 BEAST_EXPECT(
amm.expectBalances(
XRP(1'000),
USD(500),
amm.tokens()));
6481 BEAST_EXPECT(
amm.expectBalances(
6497 Env env(*
this, features);
6503 BEAST_EXPECT(
amm.expectBalances(
6514 using namespace jtx;
6528 auto const lpToken =
6530 auto const lpTokenBalance =
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6531 BEAST_EXPECT(lpToken ==
"1414.213562373095" && lpTokenBalance ==
"1414.213562373");
6532 if (!features[fixAMMv1_1])
6535 BEAST_EXPECT(
amm.ammExists());
6540 BEAST_EXPECT(!
amm.ammExists());
6546 for (
auto const& lp : {
gw_,
bob_})
6548 Env env(*
this, features);
6549 auto const abc =
gw_[
"ABC"];
6555 {
USD(1'000'000'000), abc(1'000'000'000'000)});
6556 AMM amm(env, lp, abc(2'000'000),
USD(1));
6561 auto const lpToken =
6563 auto const lpTokenBalance =
amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6564 BEAST_EXPECT(lpToken ==
"1414.213562373095" && lpTokenBalance ==
"1414.213562373");
6565 if (!features[fixAMMv1_1])
6568 BEAST_EXPECT(
amm.ammExists());
6572 amm.withdrawAll(lp);
6573 BEAST_EXPECT(!
amm.ammExists());
6580 Env env(*
this, features);
6585 BEAST_EXPECT(res && !res.value());
6587 BEAST_EXPECT(res && !res.value());
6591 Env env(*
this, features);
6596 BEAST_EXPECT(res && !res.value());
6598 BEAST_EXPECT(res && !res.value());
6602 Env env(*
this, features);
6604 auto const yan = gw1[
"YAN"];
6606 fund(env, gw1, {
gw_},
XRP(1'000), {yan(1'000)}, Fund::TokenOnly);
6610 BEAST_EXPECT(res && !res.value());
6612 BEAST_EXPECT(res && !res.value());
6619 testcase(
"test clawback from AMM account");
6620 using namespace jtx;
6623 Env env(*
this, features);
6625 env(
fset(
gw_, asfAllowTrustLineClawback));
6631 if (!features[featureAMMClawback])
6635 env(
fclear(
gw_, asfAllowTrustLineClawback));
6660 Issue const usd(
USD.issue().currency,
amm.ammAccount());
6662 env(
claw(
gw_, amount), error);
6669 testcase(
"test AMMDeposit with frozen assets");
6670 using namespace jtx;
6687 Env env(*
this, features);
6688 testAMMDeposit(env, [&](
AMM&
amm) {
6696 Env env(*
this, features);
6697 testAMMDeposit(env, [&](
AMM&
amm) {
6703 if (features[featureAMMClawback] || features[fixCleanup3_3_0])
6708 Env env(*
this, features);
6709 testAMMDeposit(env, [&](
AMM&
amm) {
6719 Env env(*
this, features);
6720 testAMMDeposit(env, [&](
AMM&
amm) {
6730 testcase(
"Fix Reserve Check On Withdrawal");
6731 using namespace jtx;
6735 auto test = [&](
auto&& cb) {
6736 Env env(*
this, features);
6737 auto const startingXrp =
reserve(env, 2) + env.
current()->fees().base * 5;
6756 .account =
alice_, .asset1Out =
EUR(0.1), .asset2Out =
USD(0.1), .err = err});
6759 .account =
alice_, .asset1Out =
USD(0.1), .asset2Out =
EUR(0.1), .err = err});
6775 testcase(
"Fail pseudo-account allocation " + suffix);
6787 for (
int i = 0; i < 256; ++i)
6812 testcase(
"Deposit and Withdraw Rounding V2");
6813 using namespace jtx;
6815 auto const xpm =
gw_[
"XPM"];
6817 STAmount xpmBalance{xpm, UINT64_C(18'610'359'80246901), -8};
6818 STAmount amount{xpm, UINT64_C(6'566'496939465400), -12};
6822 Env env(*
this, features);
6830 STAmount const lptAMMBalance{
amm.lptIssue(), UINT64_C(3'234'987'266'485968), -6};
6831 auto const burn =
IOUAmount{
amm.getLPTokensBalance() - lptAMMBalance};
6833 env(
amm.bid(
BidArg{.account = gw_, .bidMin = burn, .bidMax = burn}));
6844 auto const [amount, amount2, lptAMM] =
amm.balances(
XRP, xpm);
6845 auto const withdraw =
STAmount{xpm, 1, -5};
6847 auto const [amount_, amount2_, lptAMM_] =
amm.balances(
XRP, xpm);
6850 BEAST_EXPECT((amount2 - amount2_) > withdraw);
6854 BEAST_EXPECT((amount2 - amount2_) <= withdraw);
6863 auto const [amount, amount2, lptBalance] =
amm.balances(
GBP,
EUR);
6868 auto const res =
root2(amount * amount2);
6872 BEAST_EXPECT(res < lptBalance);
6876 BEAST_EXPECT(res >= lptBalance);
6884 using namespace jtx;
6896 [&](
AMM& ammAlice,
Env& env) {
6902 {
GBP(100'000),
EUR(100'000)},
6913 {{
GBP(30'000),
EUR(30'000)}},
6921 [&](
AMM& ammAlice,
Env& env) {
6925 STAmount const depositEuro{
EUR, UINT64_C(10'1234567890123456), -16};
6926 STAmount const depositGBP{
GBP, UINT64_C(10'1234567890123456), -16};
6929 DepositArg{.account =
bob_, .asset1In = depositEuro, .asset2In = depositGBP});
6930 invariant(ammAlice, env,
"dep2",
false);
6932 {{
GBP(30'000),
EUR(30'000)}},
6938 for (
auto const& exponent : {1, 2, 3, 4, -3 , -6, -9})
6941 [&](
AMM& ammAlice,
Env& env) {
6947 {
GBP(100'000),
EUR(100'000)},
6956 .account =
bob_, .asset1In = depositEuro, .asset2In = depositGBP});
6957 invariant(ammAlice, env,
"dep3", exponent != -3 && !env.
enabled(fixAMMv1_3));
6959 {{
GBP(10'000),
EUR(30'000)}},
6967 [&](
AMM& ammAlice,
Env& env) {
6973 invariant(ammAlice, env,
"dep4",
false);
6975 {{
GBP(7'000),
EUR(30'000)}},
6981 for (
auto const& tokens :
6992 [&](
AMM& ammAlice,
Env& env) {
6998 {
GBP(100'000),
EUR(1'000'000)},
7005 invariant(ammAlice, env,
"dep5",
false);
7007 {{
GBP(7'000),
EUR(30'000)}},
7016 [&](
AMM& ammAlice,
Env& env) {
7021 invariant(ammAlice, env,
"dep6",
false);
7023 {{
GBP(30'000),
EUR(30'000)}},
7034 using namespace jtx;
7038 [&](
AMM& ammAlice,
Env& env) {
7040 invariant(ammAlice, env,
"with1",
false);
7042 {{
GBP(7'000),
EUR(30'000)}},
7049 [&](
AMM& ammAlice,
Env& env) {
7051 invariant(ammAlice, env,
"with2",
false);
7053 {{
GBP(7'000),
EUR(30'000)}},
7060 [&](
AMM& ammAlice,
Env& env) {
7066 .flags = tfTwoAsset});
7067 invariant(ammAlice, env,
"with3",
false);
7069 {{
GBP(7'000),
EUR(30'000)}},
7080 [&](
AMM& ammAlice,
Env& env) {
7085 .flags = tfSingleAsset});
7086 invariant(ammAlice, env,
"with4",
false);
7088 {{
GBP(7'000),
EUR(30'000)}},
7095 [&](
AMM& ammAlice,
Env& env) {
7105 .flags = tfOneAssetWithdrawAll});
7106 invariant(ammAlice, env,
"with5",
false);
7108 {{
GBP(7'000),
EUR(30'000)}},
7115 [&](
AMM& ammAlice,
Env& env) {
7121 .flags = tfOneAssetLPToken});
7122 invariant(ammAlice, env,
"with6",
false);
7124 {{
GBP(7'000),
EUR(30'000)}},
7131 [&](
AMM& ammAlice,
Env& env) {
7137 .flags = tfLimitLPToken});
7138 invariant(ammAlice, env,
"with7",
true);
7140 {{
GBP(7'000),
EUR(30'000)}},
7149 testcase(
"Test AuthAccounts reset after empty pool reinitialization");
7150 using namespace jtx;
7177 .authAccounts = {bob_, dan},
7180 BEAST_EXPECT(
amm.expectAuctionSlot({bob_.id(), dan.id()}));
7183 amm.withdrawAll(alice_);
7184 amm.withdrawAll(carol_);
7185 BEAST_EXPECT(
amm.ammExists());
7188 BEAST_EXPECT(
amm.getLPTokensBalance() ==
IOUAmount{0});
7189 BEAST_EXPECT(
amm.expectAuctionSlot({bob_.id(), dan.id()}));
7203 BEAST_EXPECT(ammSle && ammSle->isFieldPresent(sfAuctionSlot));
7208 BEAST_EXPECT(
amm.expectAuctionSlot(50, 0,
IOUAmount{0}));
7211 BEAST_EXPECT(slot[sfAccount] == ed.id());
7213 BEAST_EXPECT(ammSle->getFieldU16(sfTradingFee) == 500);
7215 auto const& votes = ammSle->getFieldArray(sfVoteSlots);
7216 BEAST_EXPECT(votes.size() == 1);
7219 BEAST_EXPECT(votes[0].getAccountID(sfAccount) == ed.id());
7220 BEAST_EXPECT(votes[0].getFieldU16(sfTradingFee) == 500);
7224 if (features[fixCleanup3_2_0])
7226 BEAST_EXPECT(!slot.isFieldPresent(sfAuthAccounts));
7231 slot.isFieldPresent(sfAuthAccounts) && !slot.getFieldArray(sfAuthAccounts).empty());
7251 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)
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
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.
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
json::Value accountBalance(Env &env, Account const &acct)
json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
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 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()
NumberMantissaScaleGuard const sg
void testFixChangeSpotPriceQuality(FeatureBitset features)
void testDepositRounding(FeatureBitset all)
void testInvalidFeeVote()
void testInvalidInstance()
void testFixOverflowOffer(FeatureBitset featuresInitial)
void testInvalidWithdraw()
void testStaleAuthAccountsAfterReinit(FeatureBitset features)
void testAMMClawback(FeatureBitset features)
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.