21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
23#include <test/jtx/amount.h>
24#include <test/jtx/sendmax.h>
26#include <xrpld/app/misc/AMMHelpers.h>
27#include <xrpld/app/misc/AMMUtils.h>
28#include <xrpld/app/tx/detail/AMMBid.h>
30#include <xrpl/basics/Number.h>
31#include <xrpl/protocol/AMMCore.h>
32#include <xrpl/protocol/Feature.h>
34#include <boost/regex.hpp>
68 {{
USD(20'000),
BTC(0.5)}});
119 BEAST_EXPECT(amm.expectTradingFee(1'000));
120 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
147 BEAST_EXPECT(!ammAlice.ammExists());
156 BEAST_EXPECT(!ammAlice.ammExists());
164 BEAST_EXPECT(!ammAlice.ammExists());
181 BEAST_EXPECT(!ammAlice.ammExists());
190 BEAST_EXPECT(!ammAlice.ammExists());
199 BEAST_EXPECT(!ammAlice.ammExists());
218 BEAST_EXPECT(!ammAlice.ammExists());
243 BEAST_EXPECT(!ammAlice.ammExists());
308 auto const starting_xrp =
310 env.
fund(starting_xrp,
gw);
325 auto const starting_xrp =
327 env.
fund(starting_xrp,
gw);
382 auto const token1 = ammAlice.
lptIssue();
383 auto const token2 = ammAlice1.lptIssue();
408 auto const USD1 = gw1[
"USD"];
605 for (
auto const& it : invalidOptions)
624 jv[jss::TransactionType] = jss::AMMDeposit;
646 jv[jss::TransactionType] = jss::AMMDeposit;
651 jv[jss::LPTokenOut] =
854 [&](
AMM& ammAlice,
Env& env) {
856 if (!features[featureAMMClawback])
900 [&](
AMM& ammAlice,
Env& env) {
903 if (!features[featureAMMClawback])
962 [&](
AMM& ammAlice,
Env& env) {
1002 {{
USD(20'000),
BTC(0.5)}});
1006 Env env(*
this, features);
1021 if (features[featureAMMClawback])
1110 auto const starting_xrp =
1145 auto const starting_xrp =
1307 using namespace jtx;
1311 auto const baseFee = env.
current()->fees().base;
1325 for (
const Number deltaLPTokens :
1326 {
Number{UINT64_C(100000'0000000009), -10},
1327 Number{UINT64_C(100000'0000000001), -10}})
1333 deltaLPTokens.
mantissa(), deltaLPTokens.exponent()};
1346 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1347 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1351 const Number fr = deltaLPTokens / 1e7;
1355 const Number deltaXRP = fr * 1e10;
1356 const Number deltaUSD = fr * 1e4;
1366 XRP(10'000) + depositXRP,
1367 USD(10'000) + depositUSD,
1491 STAmount{USD, UINT64_C(10'000'000001), -6},
1607 using namespace jtx;
1610 [&](
AMM& ammAlice,
Env& env) {
1620 [&](
AMM& ammAlice,
Env& env) {
1646 .asset1Out =
USD(100),
1775 for (
auto const& it : invalidOptions)
1786 ter(std::get<5>(it)));
1937 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
2042 [&](
AMM& ammAlice,
Env&) {
2054 [&](
AMM& ammAlice,
Env&) {
2125 using namespace jtx;
2130 auto const baseFee = env.
current()->fees().base.drops();
2294 [&](
AMM& ammAlice,
Env& env) {
2298 if (!env.
current()->rules().enabled(fixAMMv1_1))
2314 ammAlice.withdrawAll(
carol);
2320 {
all,
all - fixAMMv1_1});
2324 [&](AMM& ammAlice, Env& env) {
2325 ammAlice.deposit(carol, 1'000'000);
2327 carol, USD(0), std::nullopt, IOUAmount{520, 0});
2328 if (!env.current()->rules().enabled(fixAMMv1_1))
2330 ammAlice.expectBalances(
2331 XRPAmount(11'000'000'000),
2332 STAmount{USD, UINT64_C(9'372'781065088757), -12},
2333 IOUAmount{10'153'846'15384616, -8}) &&
2334 ammAlice.expectLPTokens(
2335 carol, IOUAmount{153'846'15384616, -8}));
2338 ammAlice.expectBalances(
2339 XRPAmount(11'000'000'000),
2340 STAmount{USD, UINT64_C(9'372'781065088769), -12},
2341 IOUAmount{10'153'846'15384616, -8}) &&
2342 ammAlice.expectLPTokens(
2343 carol, IOUAmount{153'846'15384616, -8}));
2348 {all, all - fixAMMv1_1});
2353 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
2354 env(
rate(gw, 1.25));
2357 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
2358 BEAST_EXPECT(ammAlice.expectBalances(
2359 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2360 BEAST_EXPECT(
expectLine(env, alice, USD(0)));
2361 BEAST_EXPECT(
expectLine(env, alice, BTC(0)));
2362 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
2364 ammAlice.deposit(carol, 10);
2365 BEAST_EXPECT(ammAlice.expectBalances(
2366 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
2367 BEAST_EXPECT(
expectLine(env, carol, USD(0)));
2368 BEAST_EXPECT(
expectLine(env, carol, BTC(0)));
2370 ammAlice.withdraw(carol, 10);
2371 BEAST_EXPECT(ammAlice.expectBalances(
2372 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2373 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
2374 BEAST_EXPECT(
expectLine(env, carol, USD(2'000)));
2375 BEAST_EXPECT(
expectLine(env, carol, BTC(0.05)));
2379 testAMM([&](AMM& ammAlice, Env&) {
2381 ammAlice.withdraw(alice, IOUAmount{1, -3});
2382 BEAST_EXPECT(ammAlice.expectBalances(
2383 XRPAmount{9'999'999'999},
2384 STAmount{USD, UINT64_C(9'999'999999), -6},
2385 IOUAmount{9'999'999'999, -3}));
2387 testAMM([&](AMM& ammAlice, Env&) {
2389 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
2390 BEAST_EXPECT(ammAlice.expectBalances(
2391 XRPAmount{9'999'999'999},
2393 IOUAmount{9'999'999'9995, -4}));
2395 testAMM([&](AMM& ammAlice, Env&) {
2397 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
2398 BEAST_EXPECT(ammAlice.expectBalances(
2400 STAmount{USD, UINT64_C(9'999'9999999999), -10},
2401 IOUAmount{9'999'999'99999995, -8}));
2406 testAMM([&](AMM& ammAlice, Env&) {
2407 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
2408 BEAST_EXPECT(ammAlice.expectBalances(
2409 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
2412 testAMM([&](AMM& ammAlice, Env&) {
2413 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
2414 BEAST_EXPECT(ammAlice.expectBalances(
2415 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
2418 testAMM([&](AMM& ammAlice, Env&) {
2419 ammAlice.withdraw(alice, IOUAmount{9'999'900},
XRP(0));
2420 BEAST_EXPECT(ammAlice.expectBalances(
2421 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2424 testAMM([&](AMM& ammAlice, Env&) {
2426 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
2427 BEAST_EXPECT(ammAlice.expectBalances(
2428 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
2431 testAMM([&](AMM& ammAlice, Env&) {
2432 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
2433 BEAST_EXPECT(ammAlice.expectBalances(
2434 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2441 testcase(
"Invalid Fee Vote");
2442 using namespace jtx;
2444 testAMM([&](
AMM& ammAlice,
Env& env) {
2495 testAMM([&](
AMM& ammAlice,
Env& env) {
2510 testcase(
"Fee Vote");
2511 using namespace jtx;
2514 testAMM([&](
AMM& ammAlice,
Env& env) {
2516 ammAlice.
vote({}, 1'000);
2522 auto vote = [&](
AMM& ammAlice,
2525 int fundUSD = 100'000,
2529 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2531 ammAlice.
vote(a, 50 * (i + 1));
2537 testAMM([&](
AMM& ammAlice,
Env& env) {
2538 for (
int i = 0; i < 7; ++i)
2539 vote(ammAlice, env, i, 10'000);
2545 testAMM([&](
AMM& ammAlice,
Env& env) {
2546 for (
int i = 0; i < 7; ++i)
2547 vote(ammAlice, env, i);
2550 ammAlice.
vote(a, 450);
2556 testAMM([&](
AMM& ammAlice,
Env& env) {
2557 for (
int i = 0; i < 7; ++i)
2558 vote(ammAlice, env, i);
2560 vote(ammAlice, env, 7, 100'000, 20'000'000);
2566 testAMM([&](
AMM& ammAlice,
Env& env) {
2567 for (
int i = 7; i > 0; --i)
2568 vote(ammAlice, env, i);
2570 vote(ammAlice, env, 0, 100'000, 20'000'000);
2577 testAMM([&](
AMM& ammAlice,
Env& env) {
2579 for (
int i = 0; i < 7; ++i)
2580 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2582 for (
int i = 0; i < 7; ++i)
2584 ammAlice.
deposit(carol, 10'000'000);
2585 ammAlice.
vote(carol, 1'000);
2594 testAMM([&](
AMM& ammAlice,
Env& env) {
2596 for (
int i = 0; i < 7; ++i)
2597 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2599 for (
int i = 0; i < 7; ++i)
2600 ammAlice.
withdraw(accounts[i], 9'000'000);
2601 ammAlice.
deposit(carol, 1'000);
2603 ammAlice.
vote(carol, 1'000);
2604 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2606 BEAST_EXPECT(info[i][jss::account] != carol.human());
2615 testcase(
"Invalid Bid");
2616 using namespace jtx;
2622 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2623 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2626 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2633 .bidMin = 1'000'000,
2641 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2642 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2645 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2652 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2658 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2662 amm.expectBalances(
XRP(1'000), USD(1'000),
IOUAmount{1}));
2665 BEAST_EXPECT(
Number{amm.getLPTokensBalance(gw)} == 1);
2677 testAMM([&](
AMM& ammAlice,
Env& env) {
2682 .flags = tfWithdrawAll,
2684 ter(temINVALID_FLAG));
2686 ammAlice.
deposit(carol, 1'000'000);
2688 for (
auto bid : {0, -100})
2694 ter(temBAD_AMOUNT));
2699 ter(temBAD_AMOUNT));
2708 ter(tecAMM_INVALID_TOKENS));
2718 ter(terNO_ACCOUNT));
2721 Account
const dan(
"dan");
2722 env.
fund(XRP(1'000), dan);
2727 ter(tecAMM_INVALID_TOKENS));
2731 ter(tecAMM_INVALID_TOKENS));
2737 .authAccounts = {bob},
2739 ter(terNO_ACCOUNT));
2745 .assets = {{USD, GBP}},
2752 .bidMax = STAmount{USD, 100},
2754 ter(temBAD_AMM_TOKENS));
2757 .bidMin = STAmount{USD, 100},
2759 ter(temBAD_AMM_TOKENS));
2763 testAMM([&](AMM& ammAlice, Env& env) {
2764 ammAlice.withdrawAll(alice);
2773 testAMM([&](AMM& ammAlice, Env& env) {
2775 Account bill(
"bill");
2776 Account scott(
"scott");
2777 Account james(
"james");
2778 env.fund(
XRP(1'000), bob, ed, bill, scott, james);
2780 ammAlice.deposit(carol, 1'000'000);
2784 .authAccounts = {bob, ed, bill, scott, james},
2790 testAMM([&](AMM& ammAlice, Env& env) {
2791 fund(env, gw, {bob},
XRP(1'000), {USD(100)}, Fund::Acct);
2792 ammAlice.deposit(carol, 1'000'000);
2793 ammAlice.deposit(bob, 10);
2796 .bidMin = 1'000'001,
2798 ter(tecAMM_INVALID_TOKENS));
2801 .bidMax = 1'000'001,
2803 ter(tecAMM_INVALID_TOKENS));
2808 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2813 ter(tecAMM_INVALID_TOKENS));
2819 fund(env, gw, {alice, bob},
XRP(1'000), {USD(1'000)});
2821 auto const lpIssue =
amm.lptIssue();
2822 env.trust(STAmount{lpIssue, 100}, alice);
2823 env.trust(STAmount{lpIssue, 50}, bob);
2824 env(
pay(gw, alice, STAmount{lpIssue, 100}));
2825 env(
pay(gw, bob, STAmount{lpIssue, 50}));
2826 env(
amm.bid({.account = alice, .bidMin = 100}));
2833 ter(tecAMM_FAILED));
2841 using namespace jtx;
2848 [&](
AMM& ammAlice,
Env& env) {
2849 ammAlice.
deposit(carol, 1'000'000);
2850 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2863 [&](
AMM& ammAlice,
Env& env) {
2864 ammAlice.
deposit(carol, 1'000'000);
2867 {.account = carol, .bidMin = 110, .bidMax = 110}));
2873 {.account = alice, .bidMin = 180, .bidMax = 200}));
2876 XRP(11'000), USD(11'000),
IOUAmount{10'999'814'5, -1}));
2885 [&](
AMM& ammAlice,
Env& env) {
2886 ammAlice.
deposit(carol, 1'000'000);
2888 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2891 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2892 ammAlice.
deposit(bob, 1'000'000);
2894 env(ammAlice.
bid({.account = bob}));
2905 env(ammAlice.
bid({.account = carol, .bidMax = 600}));
2919 {.account = carol, .bidMin = 100, .bidMax = 600}));
2930 [&](
AMM& ammAlice,
Env& env) {
2931 ammAlice.
deposit(carol, 1'000'000);
2933 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2934 ammAlice.
deposit(bob, 1'000'000);
2939 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2943 env(ammAlice.
bid({.account = bob}));
2949 env(ammAlice.
bid({.account = carol}));
2955 env(ammAlice.
bid({.account = bob}));
2961 0, std::nullopt,
IOUAmount{127'33875, -5}));
2964 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2969 XRP(12'000), USD(12'000),
IOUAmount{11'999'678'91, -2}));
2981 [&](
AMM& ammAlice,
Env& env) {
2984 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
2985 ammAlice.
deposit(bob, 1'000'000);
2986 ammAlice.
deposit(ed, 1'000'000);
2987 ammAlice.
deposit(carol, 500'000);
2988 ammAlice.
deposit(dan, 500'000);
2993 .authAccounts = {bob, ed},
2995 auto const slotPrice =
IOUAmount{5'200};
2996 ammTokens -= slotPrice;
2999 XRP(13'000), USD(13'000), ammTokens));
3001 for (
int i = 0; i < 10; ++i)
3003 auto tokens = ammAlice.
deposit(carol, USD(100));
3004 ammAlice.
withdraw(carol, tokens, USD(0));
3005 tokens = ammAlice.
deposit(bob, USD(100));
3006 ammAlice.
withdraw(bob, tokens, USD(0));
3007 tokens = ammAlice.
deposit(ed, USD(100));
3008 ammAlice.
withdraw(ed, tokens, USD(0));
3011 if (!features[fixAMMv1_1])
3015 STAmount(USD, UINT64_C(29'499'00572620545), -11));
3018 STAmount(USD, UINT64_C(18'999'00572616195), -11));
3021 STAmount(USD, UINT64_C(18'999'00572611841), -11));
3025 STAmount(USD, UINT64_C(13'002'98282151419), -11),
3032 STAmount(USD, UINT64_C(29'499'00572620544), -11));
3035 STAmount(USD, UINT64_C(18'999'00572616194), -11));
3038 STAmount(USD, UINT64_C(18'999'0057261184), -10));
3042 STAmount(USD, UINT64_C(13'002'98282151422), -11),
3047 for (
int i = 0; i < 10; ++i)
3049 auto const tokens = ammAlice.
deposit(dan, USD(100));
3050 ammAlice.
withdraw(dan, tokens, USD(0));
3055 if (!features[fixAMMv1_1])
3059 STAmount(USD, UINT64_C(19'490'056722744), -9));
3063 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3066 ammAlice.
deposit(carol, USD(100));
3070 STAmount{USD, UINT64_C(13'112'92609877019), -11},
3072 env(pay(carol, bob, USD(100)),
3080 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3087 STAmount(USD, UINT64_C(19'490'05672274399), -11));
3091 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3094 ammAlice.
deposit(carol, USD(100));
3098 STAmount{USD, UINT64_C(13'112'92609877023), -11},
3100 env(pay(carol, bob, USD(100)),
3108 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3117 if (!features[fixAMMv1_1])
3121 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3128 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3135 if (!features[fixAMMv1_1])
3138 STAmount(USD, UINT64_C(29'399'00572620545), -11));
3142 STAmount(USD, UINT64_C(29'399'00572620544), -11));
3144 for (
int i = 0; i < 10; ++i)
3146 auto const tokens = ammAlice.
deposit(carol, USD(100));
3147 ammAlice.
withdraw(carol, tokens, USD(0));
3151 if (!features[fixAMMv1_1])
3155 STAmount(USD, UINT64_C(29'389'06197177128), -11));
3158 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3165 STAmount(USD, UINT64_C(29'389'06197177124), -11));
3168 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3176 if (!features[fixAMMv1_1])
3180 STAmount{USD, UINT64_C(13'023'98038490681), -11},
3187 STAmount{USD, UINT64_C(13'023'98038490689), -11},
3198 [&](AMM& ammAlice, Env& env) {
3201 Number{STAmount::cMinValue, STAmount::cMinOffset};
3203 {.account = alice, .bidMin = IOUAmount{tiny}}));
3206 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
3208 BEAST_EXPECT(ammAlice.expectBalances(
3209 XRP(10'000), USD(10'000), ammAlice.tokens()));
3214 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
3217 BEAST_EXPECT(ammAlice.expectAuctionSlot(
3218 0, 0, IOUAmount{tiny * Number{105, -2}}));
3221 BEAST_EXPECT(ammAlice.expectBalances(
3222 XRP(10'000), USD(10'000), ammAlice.tokens()));
3231 [&](AMM& ammAlice, Env& env) {
3234 .bidMin = IOUAmount{100},
3235 .authAccounts = {carol},
3237 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
3238 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
3239 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3242 fund(env, {bob, dan},
XRP(1'000));
3245 .bidMin = IOUAmount{100},
3246 .authAccounts = {bob, dan},
3248 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3257 Env env(*
this, features);
3258 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3259 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3260 auto const lpIssue =
amm.lptIssue();
3261 env.trust(STAmount{lpIssue, 500}, alice);
3262 env.trust(STAmount{lpIssue, 50}, bob);
3263 env(
pay(gw, alice, STAmount{lpIssue, 500}));
3264 env(
pay(gw, bob, STAmount{lpIssue, 50}));
3266 env(
amm.bid({.account = alice, .bidMin = 500}));
3267 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3268 BEAST_EXPECT(
expectLine(env, alice, STAmount{lpIssue, 0}));
3271 env(
pay(alice, bob, USD(10)), path(~USD), sendmax(
XRP(11)));
3272 BEAST_EXPECT(
amm.expectBalances(
3273 XRPAmount{1'010'010'011},
3275 IOUAmount{1'004'487'562112089, -9}));
3277 env(
pay(bob, alice,
XRP(10)), path(~XRP), sendmax(USD(11)));
3278 if (!features[fixAMMv1_1])
3280 BEAST_EXPECT(
amm.expectBalances(
3281 XRPAmount{1'000'010'011},
3282 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3283 IOUAmount{1'004'487'562112089, -9}));
3287 BEAST_EXPECT(
amm.expectBalances(
3288 XRPAmount{1'000'010'011},
3289 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3290 IOUAmount{1'004'487'562112089, -9}));
3296 Env env(*
this, features);
3297 auto const baseFee = env.current()->fees().base;
3299 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3300 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3304 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3305 env.app().config().features.erase(featureAMM);
3306 PreflightContext pfctx(
3309 env.current()->rules(),
3312 auto pf = AMMBid::preflight(pfctx);
3313 BEAST_EXPECT(pf == temDISABLED);
3314 env.app().config().features.insert(featureAMM);
3318 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3319 jtx.jv[
"TxnSignature"] =
"deadbeef";
3320 jtx.stx = env.ust(jtx);
3321 PreflightContext pfctx(
3324 env.current()->rules(),
3327 auto pf = AMMBid::preflight(pfctx);
3328 BEAST_EXPECT(pf != tesSUCCESS);
3332 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3333 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3334 jtx.jv[
"Asset2"].removeMember(
"issuer");
3335 jtx.stx = env.ust(jtx);
3336 PreflightContext pfctx(
3339 env.current()->rules(),
3342 auto pf = AMMBid::preflight(pfctx);
3343 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3351 testcase(
"Invalid AMM Payment");
3352 using namespace jtx;
3354 using namespace std::literals::chrono_literals;
3358 for (
auto const& acct : {gw, alice})
3362 fund(env, gw, {alice, carol},
XRP(1'000), {USD(100)});
3364 AMM ammAlice(env, acct,
XRP(10), USD(10));
3366 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3369 env(pay(carol, ammAlice.ammAccount(),
XRP(300)),
3372 env(pay(carol, ammAlice.ammAccount(), USD(10)),
3377 fund(env, gw, {alice, carol},
XRP(10'000'000), {USD(10'000)});
3379 AMM ammAlice(env, acct,
XRP(1'000'000), USD(100));
3381 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3384 env(pay(carol, ammAlice.ammAccount(),
XRP(1'000'000)),
3390 testAMM([&](
AMM& ammAlice,
Env& env) {
3391 auto const baseFee = env.
current()->fees().base;
3401 testAMM([&](
AMM& ammAlice,
Env& env) {
3402 auto const pk = carol.pk();
3403 auto const settleDelay = 100s;
3405 env.
current()->info().parentCloseTime + 200s;
3417 testAMM([&](
AMM& ammAlice,
Env& env) {
3424 [&](
AMM& ammAlice,
Env& env) {
3426 env(pay(alice, carol, USD(100)),
3430 env(pay(alice, carol,
XRP(100)),
3437 STAmount{USD, UINT64_C(99'999999999), -9}),
3443 STAmount{USD, UINT64_C(999'99999999), -8}),
3452 env(pay(alice, carol, USD(99.99)),
3461 {{
XRP(100), USD(100)}});
3464 testAMM([&](
AMM& ammAlice,
Env& env) {
3467 env(pay(alice, carol, USD(1)),
3472 env(pay(alice, carol,
XRP(1)),
3480 testAMM([&](
AMM& ammAlice,
Env& env) {
3486 env(pay(alice, carol, USD(1)),
3491 env(pay(alice, carol,
XRP(1)),
3499 testAMM([&](
AMM& ammAlice,
Env& env) {
3503 env(pay(alice, carol,
XRP(1)),
3514 testcase(
"Basic Payment");
3515 using namespace jtx;
3520 [&](
AMM& ammAlice,
Env& env) {
3521 env.
fund(jtx::XRP(30'000), bob);
3523 env(pay(bob, carol, USD(100)),
3529 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3531 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3534 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3536 {{
XRP(10'000), USD(10'100)}},
3543 [&](
AMM& ammAlice,
Env& env) {
3544 env.
fund(jtx::XRP(30'000), bob);
3546 env(pay(bob, carol, USD(100)),
sendmax(
XRP(100)));
3549 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3551 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3554 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3556 {{
XRP(10'000), USD(10'100)}},
3564 [&](
AMM& ammAlice,
Env& env) {
3565 env.
fund(jtx::XRP(30'000), bob);
3570 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3572 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3575 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3577 {{
XRP(10'000), USD(10'100)}},
3584 [&](
AMM& ammAlice,
Env& env) {
3585 env.
fund(jtx::XRP(30'000), bob);
3589 env(pay(bob, carol, USD(100)),
3596 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3598 BEAST_EXPECT(
expectLine(env, carol, USD(30'010)));
3602 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3606 env(pay(bob, carol, USD(100)),
3614 {{
XRP(10'000), USD(10'010)}},
3621 [&](
AMM& ammAlice,
Env& env) {
3624 env.
fund(jtx::XRP(30'000), bob);
3629 env(pay(bob, carol, USD(100)),
3636 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3641 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
3643 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3645 {{
XRP(10'000), USD(10'010)}},
3652 [&](
AMM& ammAlice,
Env& env) {
3653 env.
fund(jtx::XRP(30'000), bob);
3655 env(pay(bob, carol, USD(100)),
3661 {{
XRP(10'000), USD(10'000)}},
3671 Env env(*
this, features);
3673 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3677 auto ammEUR_XRP =
AMM(env, alice,
XRP(10'000), EUR(10'000));
3678 auto ammUSD_EUR =
AMM(env, alice, EUR(10'000), USD(10'000));
3681 env(pay(bob, carol, USD(100)),
3686 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3688 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3689 ammEUR_XRP.tokens()));
3690 if (!features[fixAMMv1_1])
3692 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3693 STAmount(USD, UINT64_C(9'970'097277662122), -12),
3694 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3695 ammUSD_EUR.tokens()));
3698 Amounts
const expectedAmounts =
3699 env.
closed()->rules().enabled(fixReducedOffersV2)
3700 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233787816), -14)}
3703 STAmount(USD, UINT64_C(29'90272233787818), -14)};
3705 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3709 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3710 STAmount(USD, UINT64_C(9'970'097277662172), -12),
3711 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3712 ammUSD_EUR.tokens()));
3715 Amounts
const expectedAmounts =
3716 env.
closed()->rules().enabled(fixReducedOffersV2)
3717 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233782839), -14)}
3720 STAmount(USD, UINT64_C(29'90272233782840), -14)};
3722 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3738 [&](
AMM& ammAlice,
Env& env) {
3741 env.
trust(EUR(2'000), alice);
3743 env(pay(gw, alice, EUR(1'000)));
3748 env(pay(bob, carol, USD(100)),
3755 STAmount(USD, UINT64_C(9'950'01249687578), -11),
3763 STAmount(EUR, UINT64_C(49'98750312422), -11)},
3765 STAmount(EUR, UINT64_C(49'98750312422), -11),
3766 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
3771 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3788 [&](
AMM& ammAlice,
Env& env) {
3789 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3793 env(pay(alice, carol, USD(200)),
3797 if (!features[fixAMMv1_1])
3800 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3802 BEAST_EXPECT(
expectLine(env, carol, USD(30'200)));
3808 STAmount(USD, UINT64_C(10'000'00000000001), -11),
3813 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
3821 ammCrtFee(env) -
txfee(env, 1)));
3824 {{
XRP(10'000), USD(10'100)}},
3833 Env env(*
this, features);
3834 fund(env, gw, {alice, bob, carol},
XRP(20'000), {USD(2'000)});
3838 AMM ammAlice(env, alice,
XRP(1'000), USD(1'050));
3839 env(pay(alice, carol, USD(200)),
3844 XRP(1'050), USD(1'000), ammAlice.
tokens()));
3845 BEAST_EXPECT(
expectLine(env, carol, USD(2'200)));
3851 [&](
AMM& ammAlice,
Env& env) {
3852 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
3854 env(offer(bob, USD(100),
XRP(100)));
3857 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3859 BEAST_EXPECT(
expectLine(env, bob, USD(1'100)));
3862 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3865 {{
XRP(10'000), USD(10'100)}},
3873 [&](
AMM& ammAlice,
Env& env) {
3874 env(
rate(gw, 1.25));
3880 env(offer(carol, EUR(100), GBP(100)));
3884 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
3886 BEAST_EXPECT(
expectLine(env, carol, GBP(29'875)));
3888 BEAST_EXPECT(
expectLine(env, carol, EUR(30'100)));
3891 {{GBP(1'000), EUR(1'100)}},
3897 [&](
AMM& amm,
Env& env) {
3898 env(
rate(gw, 1.001));
3900 env(offer(carol,
XRP(100), USD(55)));
3902 if (!features[fixAMMv1_1])
3912 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
3914 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
3925 BEAST_EXPECT(amm.expectBalances(
3927 STAmount{USD, UINT64_C(550'000000055), -9},
3936 STAmount{USD, 4'99999995, -8}}}}));
3940 STAmount(USD, UINT64_C(29'949'94999999494), -11));
3943 {{
XRP(1'000), USD(500)}},
3948 [&](
AMM& amm,
Env& env) {
3949 env(
rate(gw, 1.001));
3951 env(offer(carol,
XRP(10), USD(5.5)));
3953 if (!features[fixAMMv1_1])
3955 BEAST_EXPECT(amm.expectBalances(
3957 STAmount{USD, UINT64_C(505'050505050505), -12},
3963 BEAST_EXPECT(amm.expectBalances(
3965 STAmount{USD, UINT64_C(505'0505050505051), -13},
3970 {{
XRP(1'000), USD(500)}},
3976 [&](
AMM& ammAlice,
Env& env) {
3983 {GBP(2'000), EUR(2'000)},
3985 env(
rate(gw, 1.25));
3996 env(offer(carol, EUR(100), GBP(100)));
3998 if (!features[fixAMMv1_1])
4007 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
4008 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
4016 STAmount{EUR, UINT64_C(50'684828792831), -12},
4017 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
4029 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
4034 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
4046 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
4047 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
4055 STAmount{EUR, UINT64_C(27'06583722134028), -14},
4056 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
4068 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
4073 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
4076 BEAST_EXPECT(
expectLine(env, bob, GBP(2'010)));
4078 BEAST_EXPECT(
expectLine(env, ed, EUR(1'987.5)));
4080 {{GBP(1'000), EUR(1'100)}},
4093 [&](
AMM& ammAlice,
Env& env) {
4094 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
4095 env(
rate(gw, 1.25));
4097 env(pay(bob, carol, EUR(100)),
4103 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4105 BEAST_EXPECT(
expectLine(env, carol, EUR(30'080)));
4107 {{GBP(1'000), EUR(1'100)}},
4124 [&](
AMM& ammAlice,
Env& env) {
4127 auto const CAN = gw[
"CAN"];
4128 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
4129 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
4130 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
4131 env(trust(carol, USD(100)));
4132 env(
rate(gw, 1.25));
4134 env(offer(dan, CAN(200), GBP(200)));
4135 env(offer(ed, EUR(200), USD(200)));
4137 env(pay(bob, carol, USD(100)),
4138 path(~GBP, ~EUR, ~USD),
4143 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25), GBP(43.75)));
4145 GBP(10'125), EUR(10'000), ammAlice.
tokens()));
4146 BEAST_EXPECT(
expectLine(env, ed, EUR(300), USD(100)));
4147 BEAST_EXPECT(
expectLine(env, carol, USD(80)));
4149 {{GBP(10'000), EUR(10'125)}},
4156 [&](
AMM& ammAlice,
Env& env) {
4157 env(pay(alice, carol, USD(99.99)),
4162 env(pay(alice, carol, USD(100)),
4167 env(pay(alice, carol,
XRP(100)),
4178 {{
XRP(100), USD(100)}},
4185 Env env(*
this, features);
4186 auto const ETH = gw[
"ETH"];
4192 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4193 fund(env, gw, {carol, bob},
XRP(1'000), {USD(200)}, Fund::Acct);
4194 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4195 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4196 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4197 AMM xrp_usd(env, alice,
XRP(10'150), USD(10'200));
4198 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4199 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4200 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
4201 env(pay(bob, carol, USD(100)),
4202 path(~EUR, ~BTC, ~USD),
4204 path(~ETH, ~EUR, ~USD),
4206 if (!features[fixAMMv1_1])
4212 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
4215 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
4216 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4219 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4220 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4226 STAmount{USD, UINT64_C(10'126'06848287914), -11},
4233 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
4236 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
4237 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4240 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4241 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4247 STAmount{USD, UINT64_C(10'126'06848287943), -11},
4257 BEAST_EXPECT(xrp_eur.expectBalances(
4258 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
4260 EUR(10'000), BTC(10'200), eur_btc.
tokens()));
4262 BTC(10'100), USD(10'000), btc_usd.
tokens()));
4264 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4269 Env env(*
this, features);
4270 auto const ETH = gw[
"ETH"];
4276 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4277 fund(env, gw, {carol, bob},
XRP(1000), {USD(200)}, Fund::Acct);
4278 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4279 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4280 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4281 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4282 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4283 env(pay(bob, carol, USD(100)),
4284 path(~EUR, ~BTC, ~USD),
4285 path(~ETH, ~EUR, ~BTC, ~USD),
4287 if (!features[fixAMMv1_1])
4291 BEAST_EXPECT(xrp_eur.expectBalances(
4293 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
4296 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4297 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4300 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
4305 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
4308 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
4309 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4314 BEAST_EXPECT(xrp_eur.expectBalances(
4316 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
4319 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4320 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4323 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
4328 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
4331 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
4332 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4335 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4341 [&](
AMM& ammAlice,
Env& env) {
4343 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4344 env(trust(alice, EUR(200)));
4345 for (
int i = 0; i < 30; ++i)
4346 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4349 env(offer(alice, EUR(140),
XRP(100)));
4350 env(pay(bob, carol, USD(100)),
4354 if (!features[fixAMMv1_1])
4359 STAmount{USD, UINT64_C(9'970'089730807577), -12},
4364 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
4370 STAmount{USD, UINT64_C(9'970'089730807827), -12},
4375 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
4386 [&](
AMM& ammAlice,
Env& env) {
4388 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4389 env(trust(alice, EUR(200)));
4390 for (
int i = 0; i < 29; ++i)
4391 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4394 env(offer(alice, EUR(140),
XRP(100)));
4395 env(pay(bob, carol, USD(100)),
4401 if (!features[fixAMMv1_1])
4407 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4411 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4417 {{{
STAmount{EUR, UINT64_C(39'1858572), -7},
4428 Env env(*
this, features);
4429 fund(env, gw, {alice, carol, bob},
XRP(30'000), {USD(30'000)});
4430 env(offer(bob,
XRP(100), USD(100.001)));
4431 AMM ammAlice(env, alice,
XRP(10'000), USD(10'100));
4432 env(offer(carol, USD(100),
XRP(100)));
4433 if (!features[fixAMMv1_1])
4437 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4444 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
4450 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4457 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
4458 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4464 [&](
AMM& ammAlice,
Env& env) {
4468 env(pay(alice, carol, USD(1)),
4483 testcase(
"AMM Tokens");
4484 using namespace jtx;
4487 testAMM([&](
AMM& ammAlice,
Env& env) {
4488 auto const baseFee = env.
current()->fees().base.drops();
4489 auto const token1 = ammAlice.
lptIssue();
4496 env(offer(carol,
STAmount{token1, 5'000'000}, priceXRP));
4498 env(offer(alice, priceXRP,
STAmount{token1, 5'000'000}));
4506 ammAlice.
vote(carol, 1'000);
4508 ammAlice.
vote(carol, 0);
4511 env(ammAlice.
bid({.account = carol, .bidMin = 100}));
4536 testAMM([&](
AMM& ammAlice,
Env& env) {
4537 ammAlice.
deposit(carol, 1'000'000);
4538 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
4539 AMM ammAlice1(env, alice,
XRP(10'000), EUR(10'000));
4540 ammAlice1.deposit(carol, 1'000'000);
4541 auto const token1 = ammAlice.
lptIssue();
4542 auto const token2 = ammAlice1.lptIssue();
4562 testAMM([&](
AMM& ammAlice,
Env& env) {
4563 auto const token1 = ammAlice.
lptIssue();
4566 ammAlice.
deposit(carol, 1'000'000);
4572 env(pay(alice, carol,
STAmount{token1, 100}));
4582 env(pay(carol, alice,
STAmount{token1, 100}));
4594 testcase(
"Amendment");
4595 using namespace jtx;
4600 all - featureAMM - fixUniversalNumber};
4602 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
4604 Env env{*
this, feature};
4605 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
4623 using namespace jtx;
4625 testAMM([&](
AMM& ammAlice,
Env& env) {
4626 auto const info = env.
rpc(
4630 "{\"account\": \"" + to_string(ammAlice.
ammAccount()) +
4633 info[jss::result][jss::account_data][jss::Flags].asUInt();
4643 testcase(
"Rippling");
4644 using namespace jtx;
4660 auto const TSTA = A[
"TST"];
4661 auto const TSTB = B[
"TST"];
4670 env.
trust(TSTA(10'000), C);
4671 env.
trust(TSTB(10'000), C);
4672 env(pay(A, C, TSTA(10'000)));
4673 env(pay(B, C, TSTB(10'000)));
4674 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
4675 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
4682 env(pay(C, D,
STAmount{ammIss, 10}),
4684 path(amm.ammAccount()),
4693 testcase(
"AMMAndCLOB, offer quality change");
4694 using namespace jtx;
4695 auto const gw =
Account(
"gw");
4696 auto const TST = gw[
"TST"];
4697 auto const LP1 =
Account(
"LP1");
4698 auto const LP2 =
Account(
"LP2");
4700 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4701 Env env(*
this, features);
4702 env.
fund(
XRP(30'000'000'000), gw);
4703 env(offer(gw,
XRP(11'500'000'000), TST(1'000'000'000)));
4707 env(offer(LP1, TST(25),
XRPAmount(287'500'000)));
4712 env(offer(LP2, TST(25),
XRPAmount(287'500'000)));
4724 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
4730 lp2TakerGets = offer[
"taker_gets"].asString();
4731 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
4736 if (!features[fixAMMv1_1])
4740 STAmount{TST, UINT64_C(1'68737984885388), -14}),
4746 STAmount{TST, UINT64_C(1'68737976189735), -14}),
4755 BEAST_EXPECT(lp2TakerGets == offer[
"taker_gets"].asString());
4757 lp2TakerPays == offer[
"taker_pays"][
"value"].asString());
4764 testcase(
"Trading Fee");
4765 using namespace jtx;
4769 [&](
AMM& ammAlice,
Env& env) {
4771 ammAlice.
deposit(carol, USD(3'000));
4775 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4777 ammAlice.
vote(alice, 1'000);
4781 ammAlice.
deposit(carol, USD(3'000));
4783 carol,
IOUAmount{994'981155689671, -12}));
4784 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4786 ammAlice.
vote(alice, 0);
4792 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
4794 {{USD(1'000), EUR(1'000)}},
4802 [&](
AMM& ammAlice,
Env& env) {
4804 auto tokensFee = ammAlice.
deposit(
4805 carol, USD(1'000), std::nullopt,
STAmount{USD, 1, -1});
4808 ammAlice.
vote(alice, 0);
4810 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4813 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4814 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4824 [&](
AMM& ammAlice,
Env& env) {
4826 auto const tokensFee = ammAlice.
deposit(
4827 carol, USD(200), std::nullopt,
STAmount{USD, 2020, -6});
4830 ammAlice.
vote(alice, 0);
4832 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4835 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4836 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4845 [&](
AMM& ammAlice,
Env& env) {
4847 ammAlice.
deposit(carol, USD(3'000));
4850 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4852 ammAlice.
vote(alice, 1'000);
4859 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
4861 {{USD(1'000), EUR(1'000)}},
4868 [&](
AMM& ammAlice,
Env& env) {
4869 ammAlice.
deposit(carol, 1'000'000);
4870 auto const tokensFee = ammAlice.
withdraw(
4871 carol, USD(100), std::nullopt,
IOUAmount{520, 0});
4873 auto const balanceAfterWithdraw = [&]() {
4874 if (!features[fixAMMv1_1])
4875 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
4876 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
4878 BEAST_EXPECT(env.
balance(carol, USD) == balanceAfterWithdraw);
4880 auto const deposit = balanceAfterWithdraw - USD(29'000);
4881 ammAlice.
deposit(carol, deposit);
4883 ammAlice.
vote(alice, 0);
4885 auto const tokensNoFee = ammAlice.
withdraw(carol, deposit);
4886 if (!features[fixAMMv1_1])
4889 STAmount(USD, UINT64_C(30'443'43891402717), -11));
4893 STAmount(USD, UINT64_C(30'443'43891402716), -11));
4896 if (!features[fixAMMv1_1])
4898 tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4901 tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4902 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4911 [&](
AMM& ammAlice,
Env& env) {
4917 {USD(1'000), EUR(1'000)},
4920 BEAST_EXPECT(
expectLine(env, alice, EUR(28'990)));
4921 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4922 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4924 env(pay(carol, alice, EUR(10)),
4930 BEAST_EXPECT(
expectLine(env, alice, EUR(29'000)));
4931 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4932 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4935 ammAlice.
vote(alice, 1'000);
4938 env(pay(bob, carol, USD(10)),
4945 env, bob,
STAmount{EUR, UINT64_C(989'8989898989899), -13}));
4947 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4950 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
4953 {{USD(1'000), EUR(1'010)}},
4960 [&](
AMM& ammAlice,
Env& env) {
4962 env(offer(carol, EUR(10), USD(10)));
4964 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4965 BEAST_EXPECT(
expectLine(env, carol, EUR(30'010)));
4967 env(offer(carol, USD(10), EUR(10)));
4970 ammAlice.
vote(alice, 500);
4972 env(offer(carol, EUR(10), USD(10)));
4979 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
4983 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
4989 STAmount{EUR, UINT64_C(5'025125628140703), -15},
4990 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
4991 if (!features[fixAMMv1_1])
4994 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4995 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5001 STAmount{USD, UINT64_C(1'004'97487437186), -11},
5002 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5006 {{USD(1'000), EUR(1'010)}},
5016 Env env(*
this, features);
5021 {alice, bob, carol, ed},
5023 {USD(2'000), EUR(2'000)});
5024 env(offer(carol, EUR(5), USD(5)));
5025 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
5026 env(pay(bob, ed, USD(10)),
5030 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5031 if (!features[fixAMMv1_1])
5033 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5035 USD(1'000), EUR(1'005), ammAlice.
tokens()));
5040 env, bob,
STAmount(EUR, UINT64_C(1989'999999999999), -12)));
5043 STAmount(EUR, UINT64_C(1005'000000000001), -12),
5052 Env env(*
this, features);
5057 {alice, bob, carol, ed},
5059 {USD(2'000), EUR(2'000)});
5060 env(offer(carol, EUR(5), USD(5)));
5062 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 250);
5063 env(pay(bob, ed, USD(10)),
5067 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5068 if (!features[fixAMMv1_1])
5073 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
5076 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
5084 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
5087 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
5097 Env env(*
this, features);
5102 {alice, bob, carol, ed},
5104 {USD(2'000), EUR(2'000)});
5105 env(offer(carol, EUR(10), USD(10)));
5107 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5108 env(pay(bob, ed, USD(10)),
5112 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5113 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5115 USD(1'005), EUR(1'000), ammAlice.
tokens()));
5124 Env env(*
this, features);
5129 {alice, bob, carol, ed},
5131 {USD(2'000), EUR(2'000)});
5132 env(offer(carol, EUR(9), USD(9)));
5134 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5135 env(pay(bob, ed, USD(10)),
5139 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5141 env, bob,
STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
5144 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
5153 testcase(
"Adjusted Deposit/Withdraw Tokens");
5155 using namespace jtx;
5159 [&](
AMM& ammAlice,
Env& env) {
5167 Account const nataly(
"nataly");
5171 {bob, ed, paul, dan, chris, simon, ben, nataly},
5174 for (
int i = 0; i < 10; ++i)
5178 ammAlice.
deposit(simon, USD(0.1));
5180 ammAlice.
deposit(chris, USD(1));
5182 ammAlice.
deposit(dan, USD(10));
5184 ammAlice.
deposit(bob, USD(100));
5186 ammAlice.
deposit(carol, USD(1'000));
5188 ammAlice.
deposit(ed, USD(10'000));
5190 ammAlice.
deposit(paul, USD(100'000));
5192 ammAlice.
deposit(nataly, USD(1'000'000));
5198 if (!features[fixAMMv1_1])
5201 STAmount{USD, UINT64_C(10'000'0000000013), -10},
5206 BEAST_EXPECT(
expectLine(env, ben, USD(1'500'000)));
5207 BEAST_EXPECT(
expectLine(env, simon, USD(1'500'000)));
5208 BEAST_EXPECT(
expectLine(env, chris, USD(1'500'000)));
5209 BEAST_EXPECT(
expectLine(env, dan, USD(1'500'000)));
5210 if (!features[fixAMMv1_1])
5214 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
5216 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5217 BEAST_EXPECT(
expectLine(env, ed, USD(1'500'000)));
5218 BEAST_EXPECT(
expectLine(env, paul, USD(1'500'000)));
5219 if (!features[fixAMMv1_1])
5223 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
5228 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
5231 if (!features[fixAMMv1_1])
5235 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
5237 BEAST_EXPECT(
expectLine(env, alice, USD(30'000)));
5243 29950000000 - env.
current()->fees().base.drops()));
5251 testAMM([&](
AMM& ammAlice,
Env& env) {
5259 Account const nataly(
"nataly");
5263 {bob, ed, paul, dan, chris, simon, ben, nataly},
5267 for (
int i = 0; i < 10; ++i)
5294 auto const xrpBalance = (
XRP(2'000'000) -
txfee(env, 20)).getText();
5300 auto const baseFee = env.
current()->fees().base.drops();
5318 testcase(
"Auto Delete");
5320 using namespace jtx;
5331 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5332 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5337 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5342 amm.withdrawAll(gw);
5343 BEAST_EXPECT(amm.ammExists());
5368 env(trust(alice,
STAmount{amm.lptIssue(), 10'000}),
5383 amm.expectBalances(
XRP(10'000), USD(10'000), amm.tokens()));
5384 BEAST_EXPECT(amm.expectTradingFee(1'000));
5385 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5389 amm.withdrawAll(alice);
5390 BEAST_EXPECT(!amm.ammExists());
5391 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5402 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5403 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5408 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5412 amm.withdrawAll(gw);
5413 BEAST_EXPECT(amm.ammExists());
5417 BEAST_EXPECT(amm.ammExists());
5419 amm.ammDelete(alice);
5420 BEAST_EXPECT(!amm.ammExists());
5421 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5431 testcase(
"Clawback");
5432 using namespace jtx;
5436 AMM amm(env, gw,
XRP(1'000), USD(1'000));
5444 using namespace jtx;
5445 testAMM([&](
AMM& amm,
Env& env) {
5446 amm.setClose(
false);
5447 auto const info = env.
rpc(
5451 "{\"account\": \"" + to_string(amm.ammAccount()) +
"\"}"));
5455 info[jss::result][jss::account_data][jss::AMMID]
5456 .asString() == to_string(amm.ammID()));
5462 amm.deposit(carol, 1'000);
5463 auto affected = env.
meta()->getJson(
5464 JsonOptions::none)[sfAffectedNodes.fieldName];
5468 for (
auto const& node : affected)
5470 if (node.isMember(sfModifiedNode.fieldName) &&
5471 node[sfModifiedNode.fieldName]
5472 [sfLedgerEntryType.fieldName]
5473 .asString() ==
"AccountRoot" &&
5474 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
5476 .asString() == to_string(amm.ammAccount()))
5478 found = node[sfModifiedNode.fieldName]
5479 [sfFinalFields.fieldName][jss::AMMID]
5480 .asString() == to_string(amm.ammID());
5484 BEAST_EXPECT(found);
5496 testcase(
"Offer/Strand Selection");
5497 using namespace jtx;
5500 auto const ETH = gw1[
"ETH"];
5501 auto const CAN = gw1[
"CAN"];
5507 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5508 fund(env, gw, {alice, carol, bob, ed},
XRP(2'000), {USD(2'000)});
5513 {alice, carol, bob, ed},
5514 {ETH(2'000), CAN(2'000)},
5516 env(
rate(gw, gwRate));
5517 env(
rate(gw1, gw1Rate));
5521 for (
auto const& rates :
5536 for (
auto i = 0; i < 3; ++i)
5538 Env env(*
this, features);
5539 prep(env, rates.first, rates.second);
5541 if (i == 0 || i == 2)
5547 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5548 env(pay(carol, bob, USD(100)),
5555 BEAST_EXPECT(amm->expectBalances(
5556 USD(1'000), ETH(1'000), amm->tokens()));
5558 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5559 q[i] = Quality(Amounts{
5560 ETH(2'000) - env.
balance(carol, ETH),
5561 env.
balance(bob, USD) - USD(2'000)});
5564 BEAST_EXPECT(q[0] > q[1]);
5566 BEAST_EXPECT(q[0] == q[2]);
5573 for (
auto i = 0; i < 3; ++i)
5575 Env env(*
this, features);
5576 prep(env, rates.first, rates.second);
5578 if (i == 0 || i == 2)
5584 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5585 env(offer(alice, USD(400), ETH(400)));
5590 BEAST_EXPECT(amm->expectBalances(
5591 USD(1'000), ETH(1'000), amm->tokens()));
5593 if (i == 0 || i == 2)
5602 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
5613 for (
auto i = 0; i < 3; ++i)
5615 Env env(*
this, features);
5616 prep(env, rates.first, rates.second);
5618 if (i == 0 || i == 2)
5624 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5625 env(pay(carol, bob, USD(100)),
5632 BEAST_EXPECT(!amm->expectBalances(
5633 USD(1'000), ETH(1'000), amm->tokens()));
5635 if (i == 2 && !features[fixAMMv1_1])
5637 if (rates.first == 1.5)
5639 if (!features[fixAMMv1_1])
5647 UINT64_C(378'6327949540823),
5651 UINT64_C(283'9745962155617),
5661 UINT64_C(378'6327949540813),
5665 UINT64_C(283'974596215561),
5670 if (!features[fixAMMv1_1])
5678 UINT64_C(325'299461620749),
5682 UINT64_C(243'9745962155617),
5692 UINT64_C(325'299461620748),
5696 UINT64_C(243'974596215561),
5702 if (rates.first == 1.5)
5710 ETH, UINT64_C(378'6327949540812), -13},
5713 UINT64_C(283'9745962155609),
5724 ETH, UINT64_C(325'2994616207479), -13},
5727 UINT64_C(243'9745962155609),
5731 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5732 q[i] = Quality(Amounts{
5733 ETH(2'000) - env.
balance(carol, ETH),
5734 env.
balance(bob, USD) - USD(2'000)});
5737 BEAST_EXPECT(q[1] > q[0]);
5739 BEAST_EXPECT(q[2] > q[1]);
5743 for (
auto i = 0; i < 3; ++i)
5745 Env env(*
this, features);
5746 prep(env, rates.first, rates.second);
5748 if (i == 0 || i == 2)
5754 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5755 env(offer(alice, USD(250), ETH(400)));
5760 BEAST_EXPECT(!amm->expectBalances(
5761 USD(1'000), ETH(1'000), amm->tokens()));
5767 if (rates.first == 1.5)
5769 if (!features[fixAMMv1_1])
5772 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
5779 USD, UINT64_C(40'5694150420947), -13},
5781 ETH, UINT64_C(64'91106406735152), -14},
5795 ETH, UINT64_C(335'0889359326475), -13},
5797 USD, UINT64_C(209'4305849579047), -13},
5804 if (!features[fixAMMv1_1])
5813 ETH, UINT64_C(335'0889359326485), -13},
5815 USD, UINT64_C(209'4305849579053), -13},
5828 ETH, UINT64_C(335'0889359326475), -13},
5830 USD, UINT64_C(209'4305849579047), -13},
5856 for (
auto i = 0; i < 3; ++i)
5858 Env env(*
this, features);
5859 prep(env, rates.first, rates.second);
5862 if (i == 0 || i == 2)
5870 amm.emplace(env, ed, ETH(1'000), USD(1'000));
5872 env(pay(carol, bob, USD(100)),
5878 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5880 if (i == 2 && !features[fixAMMv1_1])
5882 if (rates.first == 1.5)
5885 BEAST_EXPECT(amm->expectBalances(
5886 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
5892 BEAST_EXPECT(amm->expectBalances(
5894 ETH, UINT64_C(1'179'540094339627), -12},
5895 STAmount{USD, UINT64_C(847'7880529867501), -13},
5904 UINT64_C(343'3179205198749),
5908 UINT64_C(343'3179205198749),
5914 UINT64_C(362'2119470132499),
5918 UINT64_C(362'2119470132499),
5925 if (rates.first == 1.5)
5928 BEAST_EXPECT(amm->expectBalances(
5930 ETH, UINT64_C(1'176'660389557593), -12},
5936 BEAST_EXPECT(amm->expectBalances(
5937 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
5938 STAmount{USD, UINT64_C(847'7880529867501), -13},
5947 UINT64_C(343'3179205198749),
5951 UINT64_C(343'3179205198749),
5957 UINT64_C(362'2119470132499),
5961 UINT64_C(362'2119470132499),
5966 q[i] = Quality(Amounts{
5967 ETH(2'000) - env.
balance(carol, ETH),
5968 env.
balance(bob, USD) - USD(2'000)});
5970 BEAST_EXPECT(q[1] > q[0]);
5971 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5979 testcase(
"Fix Default Inner Object");
5980 using namespace jtx;
5991 Env env(*
this, features);
5992 fund(env, gw, {alice},
XRP(1'000), {USD(10)});
5998 {.tfee = tfee, .close = closeLedger});
5999 amm.deposit(alice, USD(10),
XRP(10));
6002 .
account = gw, .asset1Out = USD(1), .err =
ter(err2)});
6010 .
account = gw, .asset1Out = USD(2), .err =
ter(err4)});
6017 all - fixInnerObjTemplate,
6032 all - fixInnerObjTemplate,
6044 all - fixInnerObjTemplate,
6053 all - fixInnerObjTemplate,
6067 all - fixInnerObjTemplate,
6079 testcase(
"Fix changeSpotPriceQuality");
6080 using namespace jtx;
6083 SucceedShouldSucceedResize,
6095 auto const xrpIouAmounts10_100 =
6097 auto const iouXrpAmounts10_100 =
6102 {
"0.001519763260828713",
"1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
6103 {
"0.01099814367603737",
"1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
6104 {
"0.78",
"796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
6105 {
"105439.2955578965",
"49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
6106 {
"12408293.23445213",
"4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
6107 {
"1892611",
"0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
6108 {
"423028.8508101858",
"3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
6109 {
"44565388.41001027",
"73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
6110 {
"66831.68494832662",
"16", Quality{6346111134641742975}, 0, FailShouldSucceed},
6111 {
"675.9287302203422",
"1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
6112 {
"7047.112186735699",
"1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
6113 {
"840236.4402981238",
"47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
6114 {
"992715.618909774",
"189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
6115 {
"504636667521",
"185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
6116 {
"992706.7218636649",
"189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
6117 {
"1.068737911388205",
"127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
6118 {
"17932506.56880419",
"189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
6119 {
"1.066379294658174",
"128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
6120 {
"350131413924",
"1576879.110907892", Quality{6487411636539049449}, 650,
Fail},
6121 {
"422093460",
"2.731797662057464", Quality{6702911108534394924}, 1000,
Fail},
6122 {
"76128132223",
"367172.7148422662", Quality{6487263463413514240}, 548,
Fail},
6123 {
"132701839250",
"280703770.7695443", Quality{6273750681188885075}, 562,
Fail},
6124 {
"994165.7604612011",
"189551302411", Quality{5697835592690668727}, 815,
Fail},
6125 {
"45053.33303227917",
"86612695359", Quality{5625695218943638190}, 500,
Fail},
6126 {
"199649.077043865",
"14017933007", Quality{5766034667318524880}, 324,
Fail},
6127 {
"27751824831.70903",
"78896950", Quality{6272538159621630432}, 500,
Fail},
6128 {
"225.3731275781907",
"156431793648", Quality{5477818047604078924}, 989,
Fail},
6129 {
"199649.077043865",
"14017933007", Quality{5766036094462806309}, 324,
Fail},
6130 {
"3.590272027140361",
"20677643641", Quality{5406056147042156356}, 808,
Fail},
6131 {
"1.070884664490231",
"127604712776", Quality{5268620608623825741}, 293,
Fail},
6132 {
"3272.448829820197",
"6275124076", Quality{5625710328924117902}, 81,
Fail},
6133 {
"0.009059512633902926",
"7994028", Quality{5477511954775533172}, 1000,
Fail},
6134 {
"1",
"1.0", Quality{0}, 100,
Fail},
6135 {
"1.0",
"1", Quality{0}, 100,
Fail},
6136 {
"10",
"10.0", Quality{xrpIouAmounts10_100}, 100,
Fail},
6137 {
"10.0",
"10", Quality{iouXrpAmounts10_100}, 100,
Fail},
6138 {
"69864389131",
"287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
6139 {
"4328342973",
"12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
6140 {
"32347017",
"7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
6141 {
"61697206161",
"36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
6142 {
"1654524979",
"7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
6143 {
"88621.22277293179",
"5128418948", Quality{5766347291552869205}, 380, Succeed},
6144 {
"1892611",
"0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
6145 {
"4542.639373338766",
"24554809", Quality{5838994982188783710}, 0, Succeed},
6146 {
"5132932546",
"88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
6147 {
"78929964.1549083",
"1506494795", Quality{5986890029845558688}, 589, Succeed},
6148 {
"10096561906",
"44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
6149 {
"5092.219565514988",
"8768257694", Quality{5626349534958379008}, 503, Succeed},
6150 {
"1819778294",
"8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
6151 {
"6970462.633911943",
"57359281", Quality{6054087899185946624}, 850, Succeed},
6152 {
"3983448845",
"2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
6156 {
"771493171",
"1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
6160 boost::regex rx(
"^\\d+$");
6161 boost::smatch match;
6164 Env env(*
this, features);
6165 auto rules = env.
current()->rules();
6167 for (
auto const& t : tests)
6174 auto const& quality = std::get<Quality>(t);
6175 auto const tfee = std::get<std::uint16_t>(t);
6176 auto const status = std::get<Status>(t);
6177 auto const poolInIsXRP =
6178 boost::regex_search(std::get<0>(t), match, rx);
6179 auto const poolOutIsXRP =
6180 boost::regex_search(std::get<1>(t), match, rx);
6181 assert(!(poolInIsXRP && poolOutIsXRP));
6182 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
6183 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
6187 Amounts{poolIn, poolOut},
6194 if (status == SucceedShouldSucceedResize)
6196 if (!features[fixAMMv1_1])
6197 BEAST_EXPECT(Quality{*amounts} < quality);
6199 BEAST_EXPECT(Quality{*amounts} >= quality);
6201 else if (status == Succeed)
6203 if (!features[fixAMMv1_1])
6205 Quality{*amounts} >= quality ||
6207 Quality{*amounts}, quality,
Number{1, -7}));
6209 BEAST_EXPECT(Quality{*amounts} >= quality);
6211 else if (status == FailShouldSucceed)
6214 features[fixAMMv1_1] &&
6215 Quality{*amounts} >= quality);
6217 else if (status == SucceedShouldFail)
6220 !features[fixAMMv1_1] &&
6221 Quality{*amounts} < quality &&
6223 Quality{*amounts}, quality,
Number{1, -7}));
6232 if (status ==
Fail && quality != Quality{0})
6234 auto tinyOffer = [&]() {
6241 Amounts{poolIn, poolOut},
6245 else if (
isXRP(poolOut))
6250 Amounts{poolIn, poolOut},
6255 auto const takerPays = toAmount<STAmount>(
6260 Amounts{poolIn, poolOut}, takerPays, tfee)};
6262 BEAST_EXPECT(Quality(tinyOffer) < quality);
6264 else if (status == FailShouldSucceed)
6266 BEAST_EXPECT(!features[fixAMMv1_1]);
6268 else if (status == SucceedShouldFail)
6270 BEAST_EXPECT(features[fixAMMv1_1]);
6277 !strcmp(e.
what(),
"changeSpotPriceQuality failed"));
6279 !features[fixAMMv1_1] && status == FailShouldSucceed);
6288 BEAST_EXPECT(!res.has_value());
6295 using namespace jtx;
6297 testAMM([&](
AMM& ammAlice,
Env& env) {
6305 testAMM([&](
AMM& ammAlice,
Env& env) {
6313 testAMM([&](
AMM& ammAlice,
Env& env) {
6321 testAMM([&](
AMM& ammAlice,
Env& env) {
6324 .asset2Out =
XRP(100),
6330 testAMM([&](
AMM& ammAlice,
Env& env) {
6333 .asset2Out = BAD(100),
6339 testAMM([&](
AMM& ammAlice,
Env& env) {
6341 jv[jss::TransactionType] = jss::AMMWithdraw;
6343 jv[jss::Account] = alice.human();
6345 XRP(100).value().setJson(jv[jss::Amount]);
6346 USD(100).value().setJson(jv[jss::EPrice]);
6354 using namespace jtx;
6358 Account const gatehub{
"gatehub"};
6359 Account const bitstamp{
"bitstamp"};
6360 Account const trader{
"trader"};
6361 auto const usdGH = gatehub[
"USD"];
6362 auto const btcGH = gatehub[
"BTC"];
6363 auto const usdBIT = bitstamp[
"USD"];
6367 char const* testCase;
6368 double const poolUsdBIT;
6369 double const poolUsdGH;
6381 double const offer1BtcGH = 0.1;
6382 double const offer2BtcGH = 0.1;
6383 double const offer2UsdGH = 1;
6384 double const rateBIT = 0.0;
6385 double const rateGH = 0.0;
6390 for (
auto const& input : {
6392 .testCase =
"Test Fix Overflow Offer",
6395 .sendMaxUsdBIT{usdBIT(50)},
6396 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6399 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6400 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6401 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6402 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6403 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6404 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6405 .lpTokenBalance = {28'61817604250837, -14},
6413 .testCase =
"Overflow test {1, 100, 0.111}",
6416 .sendMaxUsdBIT{usdBIT(0.111)},
6417 .sendUsdGH{usdGH, 100},
6420 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6421 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6422 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6423 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6424 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6425 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6426 .lpTokenBalance{10, 0},
6427 .offer1BtcGH = 1e-5,
6429 .offer2UsdGH = 1e-5,
6434 .testCase =
"Overflow test {1, 100, 1.00}",
6437 .sendMaxUsdBIT{usdBIT(1.00)},
6438 .sendUsdGH{usdGH, 100},
6441 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6442 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6443 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6444 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6445 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6446 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6447 .lpTokenBalance{10, 0},
6448 .offer1BtcGH = 1e-5,
6450 .offer2UsdGH = 1e-5,
6455 .testCase =
"Overflow test {1, 100, 4.6432}",
6458 .sendMaxUsdBIT{usdBIT(4.6432)},
6459 .sendUsdGH{usdGH, 100},
6462 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6463 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6464 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6465 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6466 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6467 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6468 .lpTokenBalance{10, 0},
6469 .offer1BtcGH = 1e-5,
6471 .offer2UsdGH = 1e-5,
6476 .testCase =
"Overflow test {1, 100, 10}",
6479 .sendMaxUsdBIT{usdBIT(10)},
6480 .sendUsdGH{usdGH, 100},
6483 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6484 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6485 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6486 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6487 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6488 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6489 .lpTokenBalance{10, 0},
6490 .offer1BtcGH = 1e-5,
6492 .offer2UsdGH = 1e-5,
6497 .testCase =
"Overflow test {50, 100, 5.55}",
6500 .sendMaxUsdBIT{usdBIT(5.55)},
6501 .sendUsdGH{usdGH, 100},
6504 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6505 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6506 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6507 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6508 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6509 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6510 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6511 .offer1BtcGH = 1e-5,
6513 .offer2UsdGH = 1e-5,
6518 .testCase =
"Overflow test {50, 100, 50.00}",
6521 .sendMaxUsdBIT{usdBIT(50.00)},
6522 .sendUsdGH{usdGH, 100},
6523 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6524 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6525 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6526 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6527 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6528 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6529 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6530 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6531 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6532 .offer1BtcGH = 1e-5,
6534 .offer2UsdGH = 1e-5,
6539 .testCase =
"Overflow test {50, 100, 232.16}",
6542 .sendMaxUsdBIT{usdBIT(232.16)},
6543 .sendUsdGH{usdGH, 100},
6546 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6547 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6548 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6549 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6550 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6551 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6552 .lpTokenBalance{70'71067811865475, -14},
6553 .offer1BtcGH = 1e-5,
6555 .offer2UsdGH = 1e-5,
6560 .testCase =
"Overflow test {50, 100, 500}",
6563 .sendMaxUsdBIT{usdBIT(500)},
6564 .sendUsdGH{usdGH, 100},
6567 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6568 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6569 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6570 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6571 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6572 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6573 .lpTokenBalance{70'71067811865475, -14},
6574 .offer1BtcGH = 1e-5,
6576 .offer2UsdGH = 1e-5,
6582 testcase(input.testCase);
6583 for (
auto const& features :
6584 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
6586 Env env(*
this, features);
6588 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6591 if (input.rateGH != 0.0)
6592 env(
rate(gatehub, input.rateGH));
6593 if (input.rateBIT != 0.0)
6594 env(
rate(bitstamp, input.rateBIT));
6596 env(trust(trader, usdGH(10'000'000)));
6597 env(trust(trader, usdBIT(10'000'000)));
6598 env(trust(trader, btcGH(10'000'000)));
6601 env(pay(gatehub, trader, usdGH(100'000)));
6602 env(pay(gatehub, trader, btcGH(100'000)));
6603 env(pay(bitstamp, trader, usdBIT(100'000)));
6609 usdGH(input.poolUsdGH),
6610 usdBIT(input.poolUsdBIT)};
6614 amm.getLPTokensBalance();
6616 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6619 btcGH(input.offer2BtcGH),
6620 usdGH(input.offer2UsdGH)));
6623 env(pay(trader, trader, input.sendUsdGH),
6625 path(~btcGH, ~usdGH),
6630 auto const failUsdGH =
6631 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6632 auto const failUsdBIT =
6633 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6634 auto const goodUsdGH =
6635 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6636 auto const goodUsdBIT =
6637 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6638 if (!features[fixAMMOverflowOffer])
6640 BEAST_EXPECT(amm.expectBalances(
6641 failUsdGH, failUsdBIT, input.lpTokenBalance));
6645 BEAST_EXPECT(amm.expectBalances(
6646 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
6651 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6655 Number const sqrtPoolProduct =
6656 root2(goodUsdGH * goodUsdBIT);
6667 (sqrtPoolProduct +
Number{1, -14} >=
6668 input.lpTokenBalance));
6677 testcase(
"swapRounding");
6678 using namespace jtx;
6680 const STAmount xrpPool{
XRP, UINT64_C(51600'000981)};
6681 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
6683 const STAmount xrpBob{
XRP, UINT64_C(1092'878933)};
6685 USD, UINT64_C(3'988035892323031), -28};
6688 [&](
AMM& amm,
Env& env) {
6690 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(
XRP, USD);
6693 env.
fund(xrpBob, bob);
6694 env.
trust(USD(1'000'000), bob);
6695 env(pay(gw, bob, iouBob));
6698 env(offer(bob,
XRP(6300), USD(100'000)));
6703 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
6705 {{xrpPool, iouPool}},
6708 {jtx::supported_amendments() | fixAMMv1_1});
6714 testcase(
"AMM Offer Blocked By LOB");
6715 using namespace jtx;
6721 Env env(*
this, features);
6723 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6725 env(offer(alice,
XRP(1), USD(0.01)));
6728 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6732 env(offer(carol, USD(0.49),
XRP(1)));
6735 if (!features[fixAMMv1_1])
6737 BEAST_EXPECT(amm.expectBalances(
6738 XRP(200'000), USD(100'000), amm.tokens()));
6740 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6743 env, carol, 1, {{Amounts{USD(0.49),
XRP(1)}}}));
6747 BEAST_EXPECT(amm.expectBalances(
6748 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6750 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6759 Env env(*
this, features);
6761 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6765 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6768 env(offer(carol, USD(0.49),
XRP(1)));
6772 BEAST_EXPECT(amm.expectBalances(
6773 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6780 Env env(*
this, features);
6781 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6785 env(offer(bob, USD(1),
XRPAmount(500)));
6787 AMM amm(env, alice,
XRP(1'000), USD(500));
6788 env(offer(carol,
XRP(100), USD(55)));
6790 if (!features[fixAMMv1_1])
6793 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
6795 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6797 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
6801 BEAST_EXPECT(amm.expectBalances(
6803 STAmount{USD, UINT64_C(550'000000055), -9},
6811 STAmount{USD, 4'99999995, -8}}}}));
6813 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6820 Env env(*
this, features);
6821 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6823 AMM amm(env, alice,
XRP(1'000), USD(500));
6824 env(offer(carol,
XRP(100), USD(55)));
6826 BEAST_EXPECT(amm.expectBalances(
6828 STAmount{USD, UINT64_C(550'000000055), -9},
6842 using namespace jtx;
6846 Env env(*
this, features);
6852 {USD(1'000'000'000)});
6853 AMM amm(env, gw,
XRP(2), USD(1));
6854 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6855 amm.deposit(carol,
IOUAmount{1'000'000});
6856 amm.withdrawAll(alice);
6857 amm.withdrawAll(carol);
6859 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
6860 auto const lpTokenBalance =
6861 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6863 lpToken ==
"1414.213562373095" &&
6864 lpTokenBalance ==
"1414.213562373");
6865 if (!features[fixAMMv1_1])
6868 BEAST_EXPECT(amm.ammExists());
6872 amm.withdrawAll(gw);
6873 BEAST_EXPECT(!amm.ammExists());
6879 for (
auto const& lp : {gw, bob})
6881 Env env(*
this, features);
6882 auto const ABC = gw[
"ABC"];
6886 {alice, carol, bob},
6888 {USD(1'000'000'000), ABC(1'000'000'000'000)});
6889 AMM amm(env, lp, ABC(2'000'000), USD(1));
6890 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6891 amm.deposit(carol,
IOUAmount{1'000'000});
6892 amm.withdrawAll(alice);
6893 amm.withdrawAll(carol);
6895 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
6896 auto const lpTokenBalance =
6897 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6899 lpToken ==
"1414.213562373095" &&
6900 lpTokenBalance ==
"1414.213562373");
6901 if (!features[fixAMMv1_1])
6904 BEAST_EXPECT(amm.ammExists());
6908 amm.withdrawAll(lp);
6909 BEAST_EXPECT(!amm.ammExists());
6916 Env env(*
this, features);
6917 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)});
6918 AMM amm(env, gw,
XRP(10), USD(10));
6919 amm.deposit(alice, 1'000);
6922 BEAST_EXPECT(res && !res.value());
6925 BEAST_EXPECT(res && !res.value());
6929 Env env(*
this, features);
6930 fund(env, gw, {alice},
XRP(1'000), {USD(1'000), EUR(1'000)});
6931 AMM amm(env, gw, EUR(10), USD(10));
6932 amm.deposit(alice, 1'000);
6935 BEAST_EXPECT(res && !res.value());
6938 BEAST_EXPECT(res && !res.value());
6942 Env env(*
this, features);
6944 auto const YAN = gw1[
"YAN"];
6945 fund(env, gw, {gw1},
XRP(1'000), {USD(1'000)});
6946 fund(env, gw1, {gw},
XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
6947 AMM amm(env, gw1, YAN(10), USD(10));
6948 amm.deposit(gw, 1'000);
6951 BEAST_EXPECT(res && !res.value());
6953 BEAST_EXPECT(res && !res.value());
6960 testcase(
"test clawback from AMM account");
6961 using namespace jtx;
6964 Env env(*
this, features);
6967 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
6972 if (!features[featureAMMClawback])
6998 Issue usd(USD.issue().currency, amm.ammAccount());
7007 testcase(
"test AMMDeposit with frozen assets");
7008 using namespace jtx;
7015 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7025 Env env(*
this, features);
7026 testAMMDeposit(env, [&](
AMM& amm) {
7040 Env env(*
this, features);
7041 testAMMDeposit(env, [&](
AMM& amm) {
7052 if (features[featureAMMClawback])
7057 Env env(*
this, features);
7058 testAMMDeposit(env, [&](
AMM& amm) {
7073 Env env(*
this, features);
7074 testAMMDeposit(env, [&](
AMM& amm) {
7089 testcase(
"Fix Reserve Check On Withdrawal");
7090 using namespace jtx;
7095 auto test = [&](
auto&& cb) {
7096 Env env(*
this, features);
7097 auto const starting_xrp =
7098 reserve(env, 2) + env.
current()->fees().base * 5;
7099 env.
fund(starting_xrp, gw);
7100 env.
fund(starting_xrp, alice);
7101 env.
trust(USD(2'000), alice);
7103 env(pay(gw, alice, USD(2'000)));
7105 AMM amm(env, gw, EUR(1'000), USD(1'000));
7106 amm.deposit(alice, USD(1));
7111 test([&](
AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
7114 test([&](
AMM& amm) {
7117 .asset1Out = EUR(0.1),
7118 .asset2Out = USD(0.1),
7122 .asset1Out = USD(0.1),
7123 .asset2Out = EUR(0.1),
7128 test([&](
AMM& amm) {
7130 .
account = alice, .asset1Out = EUR(0.1), .err = err});
7139 testInvalidInstance();
7140 testInstanceCreate();
7141 testInvalidDeposit(all);
7142 testInvalidDeposit(all - featureAMMClawback);
7144 testInvalidWithdraw();
7146 testInvalidFeeVote();
7150 testBid(all - fixAMMv1_1);
7151 testInvalidAMMPayment();
7152 testBasicPaymentEngine(all);
7153 testBasicPaymentEngine(all - fixAMMv1_1);
7154 testBasicPaymentEngine(all - fixReducedOffersV2);
7155 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
7160 testAMMAndCLOB(all);
7161 testAMMAndCLOB(all - fixAMMv1_1);
7162 testTradingFee(all);
7163 testTradingFee(all - fixAMMv1_1);
7164 testAdjustedTokens(all);
7165 testAdjustedTokens(all - fixAMMv1_1);
7170 testSelection(all - fixAMMv1_1);
7171 testFixDefaultInnerObj();
7173 testFixOverflowOffer(all);
7174 testFixOverflowOffer(all - fixAMMv1_1);
7176 testFixChangeSpotPriceQuality(all);
7177 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
7178 testFixAMMOfferBlockedByLOB(all);
7179 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
7180 testLPTokenBalance(all);
7181 testLPTokenBalance(all - fixAMMv1_1);
7182 testAMMClawback(all);
7183 testAMMClawback(all - featureAMMClawback);
7184 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
7185 testAMMDepositWithFrozenAssets(all);
7186 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
7187 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
7188 testFixReserveCheckOnWithdrawal(all);
7189 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
std::string asString() const
Returns the unquoted string value.
testcase_t 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.
std::int64_t mantissa() const noexcept
A currency issued by an account.
constexpr int exponent() const noexcept
constexpr rep mantissa() const noexcept
Json::Value getJson(JsonOptions) const override
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&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={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
XRPAmount ammCrtFee(jtx::Env &env) const
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Convenience class to test AMM functionality.
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
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< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
AccountID const & ammAccount() const
bool expectTradingFee(std::uint16_t fee) const
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)
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
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 getLPTokensBalance(std::optional< AccountID > const &account=std::nullopt) const
Json::Value bid(BidArg const &arg)
void setTokens(Json::Value &jv, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt)
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
NetClock::time_point now()
Returns the current network time.
beast::Journal const journal
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
void memoize(Account const &account)
Associate AccountID with account.
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
ripple::Currency currency
Sets the SendMax on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
@ objectValue
object value (collection of name/value pairs).
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Json::Value claw(Account const &account, STAmount const &amount, std::optional< Account > const &mptHolder)
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
bool expectLine(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Json::Value accountBalance(Env &env, Account const &acct)
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
std::array< std::uint8_t, 39 > constexpr cb1
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
XRP_t const XRP
Converts to XRP Issue or STAmount.
XRPAmount txfee(Env const &env, std::uint16_t n)
FeatureBitset supported_amendments()
Json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
STAmount amountFromString(Asset const &issue, std::string const &amount)
constexpr std::uint32_t tfSingleAsset
constexpr std::uint32_t asfGlobalFreeze
constexpr std::uint32_t tfOneAssetWithdrawAll
std::uint32_t constexpr TOTAL_TIME_SLOT_SECS
bool isXRP(AccountID const &c)
std::uint16_t constexpr AUCTION_SLOT_TIME_INTERVALS
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
@ Fail
Should not be retried in this ledger.
Issue getIssue(T const &amt)
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) ...
constexpr std::uint32_t tfLimitLPToken
constexpr std::uint32_t const tfBurnable
constexpr std::uint32_t tfPassive
constexpr std::uint32_t tfOneAssetLPToken
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.
constexpr std::uint32_t tfTwoAsset
constexpr std::uint32_t tfPartialPayment
constexpr std::uint32_t tfWithdrawAll
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
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...
constexpr std::uint32_t tfSetfAuth
constexpr std::uint32_t asfDefaultRipple
constexpr std::uint32_t tfClearFreeze
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
@ tecINSUFFICIENT_RESERVE
constexpr std::uint32_t tfLPToken
constexpr std::uint32_t tfNoRippleDirect
std::uint32_t constexpr AUCTION_SLOT_INTERVAL_DURATION
constexpr std::uint32_t tfLimitQuality
constexpr std::uint32_t tfTwoAssetIfEmpty
constexpr std::uint32_t asfAllowTrustLineClawback
std::uint16_t constexpr maxDeletableAMMTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
constexpr std::uint32_t asfRequireAuth
constexpr std::uint32_t tfSetFreeze
STAmount withdrawByTokens(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
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.
Zero allows classes to offer efficient comparisons to zero.
Basic tests of AMM that do not use offers.
void testBid(FeatureBitset features)
void testInvalidDeposit(FeatureBitset features)
void testInvalidAMMPayment()
void testSelection(FeatureBitset features)
void testAMMClawback(FeatureBitset features)
void testInvalidFeeVote()
void testLPTokenBalance(FeatureBitset features)
void run() override
Runs the suite.
void testInstanceCreate()
void testTradingFee(FeatureBitset features)
void testFixOverflowOffer(FeatureBitset features)
void testInvalidWithdraw()
void testAMMAndCLOB(FeatureBitset features)
void testInvalidInstance()
void testBasicPaymentEngine(FeatureBitset features)
void testFixChangeSpotPriceQuality(FeatureBitset features)
void testFixDefaultInnerObj()
void testFixReserveCheckOnWithdrawal(FeatureBitset features)
void testAdjustedTokens(FeatureBitset features)
void testAMMDepositWithFrozenAssets(FeatureBitset features)
void testFixAMMOfferBlockedByLOB(FeatureBitset features)
std::optional< Account > account
std::optional< STAmount > asset1In
std::optional< Account > account
std::optional< Account > account
std::optional< std::uint32_t > flags
std::optional< STAmount > asset1Out
std::optional< LPToken > tokens
Set the "CancelAfter" time tag on a JTx.
Set the "FinishAfter" time tag on a JTx.
Set the sequence number on a JTx.