21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
23#include <test/jtx/amount.h>
24#include <test/jtx/sendmax.h>
25#include <xrpld/app/misc/AMMHelpers.h>
26#include <xrpld/app/misc/AMMUtils.h>
27#include <xrpld/app/tx/detail/AMMBid.h>
28#include <xrpl/basics/Number.h>
29#include <xrpl/protocol/AMMCore.h>
30#include <xrpl/protocol/Feature.h>
32#include <boost/regex.hpp>
66 {{
USD(20'000),
BTC(0.5)}});
117 BEAST_EXPECT(amm.expectTradingFee(1'000));
118 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
145 BEAST_EXPECT(!ammAlice.ammExists());
154 BEAST_EXPECT(!ammAlice.ammExists());
162 BEAST_EXPECT(!ammAlice.ammExists());
179 BEAST_EXPECT(!ammAlice.ammExists());
188 BEAST_EXPECT(!ammAlice.ammExists());
197 BEAST_EXPECT(!ammAlice.ammExists());
216 BEAST_EXPECT(!ammAlice.ammExists());
241 BEAST_EXPECT(!ammAlice.ammExists());
306 auto const starting_xrp =
308 env.
fund(starting_xrp,
gw);
323 auto const starting_xrp =
325 env.
fund(starting_xrp,
gw);
380 auto const token1 = ammAlice.
lptIssue();
381 auto const token2 = ammAlice1.lptIssue();
406 auto const USD1 = gw1[
"USD"];
603 for (
auto const& it : invalidOptions)
622 jv[jss::TransactionType] = jss::AMMDeposit;
644 jv[jss::TransactionType] = jss::AMMDeposit;
649 jv[jss::LPTokenOut] =
852 [&](
AMM& ammAlice,
Env& env) {
854 if (!features[featureAMMClawback])
898 [&](
AMM& ammAlice,
Env& env) {
901 if (!features[featureAMMClawback])
960 [&](
AMM& ammAlice,
Env& env) {
1000 {{
USD(20'000),
BTC(0.5)}});
1004 Env env(*
this, features);
1019 if (features[featureAMMClawback])
1108 auto const starting_xrp =
1143 auto const starting_xrp =
1305 using namespace jtx;
1322 for (
const Number deltaLPTokens :
1323 {
Number{UINT64_C(100000'0000000009), -10},
1324 Number{UINT64_C(100000'0000000001), -10}})
1330 deltaLPTokens.
mantissa(), deltaLPTokens.exponent()};
1343 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1344 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1348 const Number fr = deltaLPTokens / 1e7;
1352 const Number deltaXRP = fr * 1e10;
1353 const Number deltaUSD = fr * 1e4;
1363 XRP(10'000) + depositXRP,
1364 USD(10'000) + depositUSD,
1488 STAmount{USD, UINT64_C(10'000'000001), -6},
1604 using namespace jtx;
1607 [&](
AMM& ammAlice,
Env& env) {
1617 [&](
AMM& ammAlice,
Env& env) {
1642 .asset1Out =
USD(100),
1771 for (
auto const& it : invalidOptions)
1782 ter(std::get<5>(it)));
1933 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
2038 [&](
AMM& ammAlice,
Env&) {
2050 [&](
AMM& ammAlice,
Env&) {
2121 using namespace jtx;
2289 [&](
AMM& ammAlice,
Env& env) {
2293 if (!env.
current()->rules().enabled(fixAMMv1_1))
2309 ammAlice.withdrawAll(
carol);
2315 {
all,
all - fixAMMv1_1});
2319 [&](AMM& ammAlice, Env& env) {
2320 ammAlice.deposit(carol, 1'000'000);
2322 carol, USD(0), std::nullopt, IOUAmount{520, 0});
2323 if (!env.current()->rules().enabled(fixAMMv1_1))
2325 ammAlice.expectBalances(
2326 XRPAmount(11'000'000'000),
2327 STAmount{USD, UINT64_C(9'372'781065088757), -12},
2328 IOUAmount{10'153'846'15384616, -8}) &&
2329 ammAlice.expectLPTokens(
2330 carol, IOUAmount{153'846'15384616, -8}));
2333 ammAlice.expectBalances(
2334 XRPAmount(11'000'000'000),
2335 STAmount{USD, UINT64_C(9'372'781065088769), -12},
2336 IOUAmount{10'153'846'15384616, -8}) &&
2337 ammAlice.expectLPTokens(
2338 carol, IOUAmount{153'846'15384616, -8}));
2343 {all, all - fixAMMv1_1});
2348 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
2349 env(
rate(gw, 1.25));
2352 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
2353 BEAST_EXPECT(ammAlice.expectBalances(
2354 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2355 BEAST_EXPECT(
expectLine(env, alice, USD(0)));
2356 BEAST_EXPECT(
expectLine(env, alice, BTC(0)));
2357 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
2359 ammAlice.deposit(carol, 10);
2360 BEAST_EXPECT(ammAlice.expectBalances(
2361 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
2362 BEAST_EXPECT(
expectLine(env, carol, USD(0)));
2363 BEAST_EXPECT(
expectLine(env, carol, BTC(0)));
2365 ammAlice.withdraw(carol, 10);
2366 BEAST_EXPECT(ammAlice.expectBalances(
2367 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2368 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
2369 BEAST_EXPECT(
expectLine(env, carol, USD(2'000)));
2370 BEAST_EXPECT(
expectLine(env, carol, BTC(0.05)));
2374 testAMM([&](AMM& ammAlice, Env&) {
2376 ammAlice.withdraw(alice, IOUAmount{1, -3});
2377 BEAST_EXPECT(ammAlice.expectBalances(
2378 XRPAmount{9'999'999'999},
2379 STAmount{USD, UINT64_C(9'999'999999), -6},
2380 IOUAmount{9'999'999'999, -3}));
2382 testAMM([&](AMM& ammAlice, Env&) {
2384 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
2385 BEAST_EXPECT(ammAlice.expectBalances(
2386 XRPAmount{9'999'999'999},
2388 IOUAmount{9'999'999'9995, -4}));
2390 testAMM([&](AMM& ammAlice, Env&) {
2392 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
2393 BEAST_EXPECT(ammAlice.expectBalances(
2395 STAmount{USD, UINT64_C(9'999'9999999999), -10},
2396 IOUAmount{9'999'999'99999995, -8}));
2401 testAMM([&](AMM& ammAlice, Env&) {
2402 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
2403 BEAST_EXPECT(ammAlice.expectBalances(
2404 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
2407 testAMM([&](AMM& ammAlice, Env&) {
2408 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
2409 BEAST_EXPECT(ammAlice.expectBalances(
2410 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
2413 testAMM([&](AMM& ammAlice, Env&) {
2414 ammAlice.withdraw(alice, IOUAmount{9'999'900},
XRP(0));
2415 BEAST_EXPECT(ammAlice.expectBalances(
2416 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2419 testAMM([&](AMM& ammAlice, Env&) {
2421 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
2422 BEAST_EXPECT(ammAlice.expectBalances(
2423 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
2426 testAMM([&](AMM& ammAlice, Env&) {
2427 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
2428 BEAST_EXPECT(ammAlice.expectBalances(
2429 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2436 testcase(
"Invalid Fee Vote");
2437 using namespace jtx;
2439 testAMM([&](
AMM& ammAlice,
Env& env) {
2490 testAMM([&](
AMM& ammAlice,
Env& env) {
2505 testcase(
"Fee Vote");
2506 using namespace jtx;
2509 testAMM([&](
AMM& ammAlice,
Env& env) {
2511 ammAlice.
vote({}, 1'000);
2517 auto vote = [&](
AMM& ammAlice,
2520 int fundUSD = 100'000,
2524 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2526 ammAlice.
vote(a, 50 * (i + 1));
2532 testAMM([&](
AMM& ammAlice,
Env& env) {
2533 for (
int i = 0; i < 7; ++i)
2534 vote(ammAlice, env, i, 10'000);
2540 testAMM([&](
AMM& ammAlice,
Env& env) {
2541 for (
int i = 0; i < 7; ++i)
2542 vote(ammAlice, env, i);
2545 ammAlice.
vote(a, 450);
2551 testAMM([&](
AMM& ammAlice,
Env& env) {
2552 for (
int i = 0; i < 7; ++i)
2553 vote(ammAlice, env, i);
2555 vote(ammAlice, env, 7, 100'000, 20'000'000);
2561 testAMM([&](
AMM& ammAlice,
Env& env) {
2562 for (
int i = 7; i > 0; --i)
2563 vote(ammAlice, env, i);
2565 vote(ammAlice, env, 0, 100'000, 20'000'000);
2572 testAMM([&](
AMM& ammAlice,
Env& env) {
2574 for (
int i = 0; i < 7; ++i)
2575 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2577 for (
int i = 0; i < 7; ++i)
2579 ammAlice.
deposit(carol, 10'000'000);
2580 ammAlice.
vote(carol, 1'000);
2589 testAMM([&](
AMM& ammAlice,
Env& env) {
2591 for (
int i = 0; i < 7; ++i)
2592 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2594 for (
int i = 0; i < 7; ++i)
2595 ammAlice.
withdraw(accounts[i], 9'000'000);
2596 ammAlice.
deposit(carol, 1'000);
2598 ammAlice.
vote(carol, 1'000);
2599 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2601 BEAST_EXPECT(info[i][jss::account] != carol.human());
2610 testcase(
"Invalid Bid");
2611 using namespace jtx;
2617 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2618 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2621 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2628 .bidMin = 1'000'000,
2636 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2637 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2640 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2647 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2653 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2657 amm.expectBalances(
XRP(1'000), USD(1'000),
IOUAmount{1}));
2660 BEAST_EXPECT(
Number{amm.getLPTokensBalance(gw)} == 1);
2672 testAMM([&](
AMM& ammAlice,
Env& env) {
2677 .flags = tfWithdrawAll,
2679 ter(temINVALID_FLAG));
2681 ammAlice.
deposit(carol, 1'000'000);
2683 for (
auto bid : {0, -100})
2689 ter(temBAD_AMOUNT));
2694 ter(temBAD_AMOUNT));
2703 ter(tecAMM_INVALID_TOKENS));
2713 ter(terNO_ACCOUNT));
2716 Account
const dan(
"dan");
2717 env.
fund(XRP(1'000), dan);
2722 ter(tecAMM_INVALID_TOKENS));
2726 ter(tecAMM_INVALID_TOKENS));
2732 .authAccounts = {bob},
2734 ter(terNO_ACCOUNT));
2740 .assets = {{USD, GBP}},
2747 .bidMax = STAmount{USD, 100},
2749 ter(temBAD_AMM_TOKENS));
2752 .bidMin = STAmount{USD, 100},
2754 ter(temBAD_AMM_TOKENS));
2758 testAMM([&](AMM& ammAlice, Env& env) {
2759 ammAlice.withdrawAll(alice);
2768 testAMM([&](AMM& ammAlice, Env& env) {
2770 Account bill(
"bill");
2771 Account scott(
"scott");
2772 Account james(
"james");
2773 env.fund(
XRP(1'000), bob, ed, bill, scott, james);
2775 ammAlice.deposit(carol, 1'000'000);
2779 .authAccounts = {bob, ed, bill, scott, james},
2785 testAMM([&](AMM& ammAlice, Env& env) {
2786 fund(env, gw, {bob},
XRP(1'000), {USD(100)}, Fund::Acct);
2787 ammAlice.deposit(carol, 1'000'000);
2788 ammAlice.deposit(bob, 10);
2791 .bidMin = 1'000'001,
2793 ter(tecAMM_INVALID_TOKENS));
2796 .bidMax = 1'000'001,
2798 ter(tecAMM_INVALID_TOKENS));
2803 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2808 ter(tecAMM_INVALID_TOKENS));
2814 fund(env, gw, {alice, bob},
XRP(1'000), {USD(1'000)});
2816 auto const lpIssue =
amm.lptIssue();
2817 env.trust(STAmount{lpIssue, 100}, alice);
2818 env.trust(STAmount{lpIssue, 50}, bob);
2819 env(
pay(gw, alice, STAmount{lpIssue, 100}));
2820 env(
pay(gw, bob, STAmount{lpIssue, 50}));
2821 env(
amm.bid({.account = alice, .bidMin = 100}));
2828 ter(tecAMM_FAILED));
2836 using namespace jtx;
2843 [&](
AMM& ammAlice,
Env& env) {
2844 ammAlice.
deposit(carol, 1'000'000);
2845 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2858 [&](
AMM& ammAlice,
Env& env) {
2859 ammAlice.
deposit(carol, 1'000'000);
2862 {.account = carol, .bidMin = 110, .bidMax = 110}));
2868 {.account = alice, .bidMin = 180, .bidMax = 200}));
2871 XRP(11'000), USD(11'000),
IOUAmount{10'999'814'5, -1}));
2880 [&](
AMM& ammAlice,
Env& env) {
2881 ammAlice.
deposit(carol, 1'000'000);
2883 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2886 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2887 ammAlice.
deposit(bob, 1'000'000);
2889 env(ammAlice.
bid({.account = bob}));
2900 env(ammAlice.
bid({.account = carol, .bidMax = 600}));
2914 {.account = carol, .bidMin = 100, .bidMax = 600}));
2925 [&](
AMM& ammAlice,
Env& env) {
2926 ammAlice.
deposit(carol, 1'000'000);
2928 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2929 ammAlice.
deposit(bob, 1'000'000);
2934 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2938 env(ammAlice.
bid({.account = bob}));
2944 env(ammAlice.
bid({.account = carol}));
2950 env(ammAlice.
bid({.account = bob}));
2956 0, std::nullopt,
IOUAmount{127'33875, -5}));
2959 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2964 XRP(12'000), USD(12'000),
IOUAmount{11'999'678'91, -2}));
2976 [&](
AMM& ammAlice,
Env& env) {
2979 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
2980 ammAlice.
deposit(bob, 1'000'000);
2981 ammAlice.
deposit(ed, 1'000'000);
2982 ammAlice.
deposit(carol, 500'000);
2983 ammAlice.
deposit(dan, 500'000);
2988 .authAccounts = {bob, ed},
2990 auto const slotPrice =
IOUAmount{5'200};
2991 ammTokens -= slotPrice;
2994 XRP(13'000), USD(13'000), ammTokens));
2996 for (
int i = 0; i < 10; ++i)
2998 auto tokens = ammAlice.
deposit(carol, USD(100));
2999 ammAlice.
withdraw(carol, tokens, USD(0));
3000 tokens = ammAlice.
deposit(bob, USD(100));
3001 ammAlice.
withdraw(bob, tokens, USD(0));
3002 tokens = ammAlice.
deposit(ed, USD(100));
3003 ammAlice.
withdraw(ed, tokens, USD(0));
3006 if (!features[fixAMMv1_1])
3010 STAmount(USD, UINT64_C(29'499'00572620545), -11));
3013 STAmount(USD, UINT64_C(18'999'00572616195), -11));
3016 STAmount(USD, UINT64_C(18'999'00572611841), -11));
3020 STAmount(USD, UINT64_C(13'002'98282151419), -11),
3027 STAmount(USD, UINT64_C(29'499'00572620544), -11));
3030 STAmount(USD, UINT64_C(18'999'00572616194), -11));
3033 STAmount(USD, UINT64_C(18'999'0057261184), -10));
3037 STAmount(USD, UINT64_C(13'002'98282151422), -11),
3042 for (
int i = 0; i < 10; ++i)
3044 auto const tokens = ammAlice.
deposit(dan, USD(100));
3045 ammAlice.
withdraw(dan, tokens, USD(0));
3050 if (!features[fixAMMv1_1])
3054 STAmount(USD, UINT64_C(19'490'056722744), -9));
3058 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3061 ammAlice.
deposit(carol, USD(100));
3065 STAmount{USD, UINT64_C(13'112'92609877019), -11},
3067 env(pay(carol, bob, USD(100)),
3075 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3082 STAmount(USD, UINT64_C(19'490'05672274399), -11));
3086 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3089 ammAlice.
deposit(carol, USD(100));
3093 STAmount{USD, UINT64_C(13'112'92609877023), -11},
3095 env(pay(carol, bob, USD(100)),
3103 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3112 if (!features[fixAMMv1_1])
3116 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3123 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3130 if (!features[fixAMMv1_1])
3133 STAmount(USD, UINT64_C(29'399'00572620545), -11));
3137 STAmount(USD, UINT64_C(29'399'00572620544), -11));
3139 for (
int i = 0; i < 10; ++i)
3141 auto const tokens = ammAlice.
deposit(carol, USD(100));
3142 ammAlice.
withdraw(carol, tokens, USD(0));
3146 if (!features[fixAMMv1_1])
3150 STAmount(USD, UINT64_C(29'389'06197177128), -11));
3153 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3160 STAmount(USD, UINT64_C(29'389'06197177124), -11));
3163 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3171 if (!features[fixAMMv1_1])
3175 STAmount{USD, UINT64_C(13'023'98038490681), -11},
3182 STAmount{USD, UINT64_C(13'023'98038490689), -11},
3193 [&](AMM& ammAlice, Env& env) {
3196 Number{STAmount::cMinValue, STAmount::cMinOffset};
3198 {.account = alice, .bidMin = IOUAmount{tiny}}));
3201 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
3203 BEAST_EXPECT(ammAlice.expectBalances(
3204 XRP(10'000), USD(10'000), ammAlice.tokens()));
3209 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
3212 BEAST_EXPECT(ammAlice.expectAuctionSlot(
3213 0, 0, IOUAmount{tiny * Number{105, -2}}));
3216 BEAST_EXPECT(ammAlice.expectBalances(
3217 XRP(10'000), USD(10'000), ammAlice.tokens()));
3226 [&](AMM& ammAlice, Env& env) {
3229 .bidMin = IOUAmount{100},
3230 .authAccounts = {carol},
3232 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
3233 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
3234 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3237 fund(env, {bob, dan},
XRP(1'000));
3240 .bidMin = IOUAmount{100},
3241 .authAccounts = {bob, dan},
3243 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3252 Env env(*
this, features);
3253 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3254 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3255 auto const lpIssue =
amm.lptIssue();
3256 env.trust(STAmount{lpIssue, 500}, alice);
3257 env.trust(STAmount{lpIssue, 50}, bob);
3258 env(
pay(gw, alice, STAmount{lpIssue, 500}));
3259 env(
pay(gw, bob, STAmount{lpIssue, 50}));
3261 env(
amm.bid({.account = alice, .bidMin = 500}));
3262 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3263 BEAST_EXPECT(
expectLine(env, alice, STAmount{lpIssue, 0}));
3266 env(
pay(alice, bob, USD(10)), path(~USD), sendmax(
XRP(11)));
3267 BEAST_EXPECT(
amm.expectBalances(
3268 XRPAmount{1'010'010'011},
3270 IOUAmount{1'004'487'562112089, -9}));
3272 env(
pay(bob, alice,
XRP(10)), path(~XRP), sendmax(USD(11)));
3273 if (!features[fixAMMv1_1])
3275 BEAST_EXPECT(
amm.expectBalances(
3276 XRPAmount{1'000'010'011},
3277 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3278 IOUAmount{1'004'487'562112089, -9}));
3282 BEAST_EXPECT(
amm.expectBalances(
3283 XRPAmount{1'000'010'011},
3284 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3285 IOUAmount{1'004'487'562112089, -9}));
3291 Env env(*
this, features);
3292 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3293 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3297 auto jtx = env.jt(tx, seq(1), fee(10));
3298 env.app().config().features.erase(featureAMM);
3299 PreflightContext pfctx(
3302 env.current()->rules(),
3305 auto pf = AMMBid::preflight(pfctx);
3306 BEAST_EXPECT(pf == temDISABLED);
3307 env.app().config().features.insert(featureAMM);
3311 auto jtx = env.jt(tx, seq(1), fee(10));
3312 jtx.jv[
"TxnSignature"] =
"deadbeef";
3313 jtx.stx = env.ust(jtx);
3314 PreflightContext pfctx(
3317 env.current()->rules(),
3320 auto pf = AMMBid::preflight(pfctx);
3321 BEAST_EXPECT(pf != tesSUCCESS);
3325 auto jtx = env.jt(tx, seq(1), fee(10));
3326 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3327 jtx.jv[
"Asset2"].removeMember(
"issuer");
3328 jtx.stx = env.ust(jtx);
3329 PreflightContext pfctx(
3332 env.current()->rules(),
3335 auto pf = AMMBid::preflight(pfctx);
3336 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3344 testcase(
"Invalid AMM Payment");
3345 using namespace jtx;
3347 using namespace std::literals::chrono_literals;
3351 for (
auto const& acct : {gw, alice})
3355 fund(env, gw, {alice, carol},
XRP(1'000), {USD(100)});
3357 AMM ammAlice(env, acct,
XRP(10), USD(10));
3359 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3362 env(pay(carol, ammAlice.ammAccount(),
XRP(300)),
3365 env(pay(carol, ammAlice.ammAccount(), USD(10)),
3370 fund(env, gw, {alice, carol},
XRP(10'000'000), {USD(10'000)});
3372 AMM ammAlice(env, acct,
XRP(1'000'000), USD(100));
3374 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3377 env(pay(carol, ammAlice.ammAccount(),
XRP(1'000'000)),
3383 testAMM([&](
AMM& ammAlice,
Env& env) {
3393 testAMM([&](
AMM& ammAlice,
Env& env) {
3394 auto const pk = carol.pk();
3395 auto const settleDelay = 100s;
3397 env.
current()->info().parentCloseTime + 200s;
3409 testAMM([&](
AMM& ammAlice,
Env& env) {
3416 [&](
AMM& ammAlice,
Env& env) {
3418 env(pay(alice, carol, USD(100)),
3422 env(pay(alice, carol,
XRP(100)),
3429 STAmount{USD, UINT64_C(99'999999999), -9}),
3435 STAmount{USD, UINT64_C(999'99999999), -8}),
3444 env(pay(alice, carol, USD(99.99)),
3453 {{
XRP(100), USD(100)}});
3456 testAMM([&](
AMM& ammAlice,
Env& env) {
3459 env(pay(alice, carol, USD(1)),
3464 env(pay(alice, carol,
XRP(1)),
3472 testAMM([&](
AMM& ammAlice,
Env& env) {
3478 env(pay(alice, carol, USD(1)),
3483 env(pay(alice, carol,
XRP(1)),
3491 testAMM([&](
AMM& ammAlice,
Env& env) {
3495 env(pay(alice, carol,
XRP(1)),
3506 testcase(
"Basic Payment");
3507 using namespace jtx;
3512 [&](
AMM& ammAlice,
Env& env) {
3513 env.
fund(jtx::XRP(30'000), bob);
3515 env(pay(bob, carol, USD(100)),
3521 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3523 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3526 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3528 {{
XRP(10'000), USD(10'100)}},
3535 [&](
AMM& ammAlice,
Env& env) {
3536 env.
fund(jtx::XRP(30'000), bob);
3538 env(pay(bob, carol, USD(100)),
sendmax(
XRP(100)));
3541 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3543 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3546 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3548 {{
XRP(10'000), USD(10'100)}},
3556 [&](
AMM& ammAlice,
Env& env) {
3557 env.
fund(jtx::XRP(30'000), bob);
3562 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3564 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3567 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3569 {{
XRP(10'000), USD(10'100)}},
3576 [&](
AMM& ammAlice,
Env& env) {
3577 env.
fund(jtx::XRP(30'000), bob);
3581 env(pay(bob, carol, USD(100)),
3588 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3590 BEAST_EXPECT(
expectLine(env, carol, USD(30'010)));
3594 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3598 env(pay(bob, carol, USD(100)),
3606 {{
XRP(10'000), USD(10'010)}},
3613 [&](
AMM& ammAlice,
Env& env) {
3616 env.
fund(jtx::XRP(30'000), bob);
3621 env(pay(bob, carol, USD(100)),
3628 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3633 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
3635 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3637 {{
XRP(10'000), USD(10'010)}},
3644 [&](
AMM& ammAlice,
Env& env) {
3645 env.
fund(jtx::XRP(30'000), bob);
3647 env(pay(bob, carol, USD(100)),
3653 {{
XRP(10'000), USD(10'000)}},
3663 Env env(*
this, features);
3665 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3669 auto ammEUR_XRP =
AMM(env, alice,
XRP(10'000), EUR(10'000));
3670 auto ammUSD_EUR =
AMM(env, alice, EUR(10'000), USD(10'000));
3673 env(pay(bob, carol, USD(100)),
3678 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3680 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3681 ammEUR_XRP.tokens()));
3682 if (!features[fixAMMv1_1])
3684 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3685 STAmount(USD, UINT64_C(9'970'097277662122), -12),
3686 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3687 ammUSD_EUR.tokens()));
3690 Amounts
const expectedAmounts =
3691 env.
closed()->rules().enabled(fixReducedOffersV2)
3692 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233787816), -14)}
3695 STAmount(USD, UINT64_C(29'90272233787818), -14)};
3697 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3701 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3702 STAmount(USD, UINT64_C(9'970'097277662172), -12),
3703 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3704 ammUSD_EUR.tokens()));
3707 Amounts
const expectedAmounts =
3708 env.
closed()->rules().enabled(fixReducedOffersV2)
3709 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233782839), -14)}
3712 STAmount(USD, UINT64_C(29'90272233782840), -14)};
3714 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3730 [&](
AMM& ammAlice,
Env& env) {
3733 env.
trust(EUR(2'000), alice);
3735 env(pay(gw, alice, EUR(1'000)));
3740 env(pay(bob, carol, USD(100)),
3747 STAmount(USD, UINT64_C(9'950'01249687578), -11),
3755 STAmount(EUR, UINT64_C(49'98750312422), -11)},
3757 STAmount(EUR, UINT64_C(49'98750312422), -11),
3758 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
3763 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3780 [&](
AMM& ammAlice,
Env& env) {
3781 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3785 env(pay(alice, carol, USD(200)),
3789 if (!features[fixAMMv1_1])
3792 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3794 BEAST_EXPECT(
expectLine(env, carol, USD(30'200)));
3800 STAmount(USD, UINT64_C(10'000'00000000001), -11),
3805 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
3813 ammCrtFee(env) -
txfee(env, 1)));
3816 {{
XRP(10'000), USD(10'100)}},
3825 Env env(*
this, features);
3826 fund(env, gw, {alice, bob, carol},
XRP(20'000), {USD(2'000)});
3828 AMM ammAlice(env, alice,
XRP(1'000), USD(1'050));
3829 env(pay(alice, carol, USD(200)),
3833 XRP(1'050), USD(1'000), ammAlice.
tokens()));
3834 BEAST_EXPECT(
expectLine(env, carol, USD(2'200)));
3840 [&](
AMM& ammAlice,
Env& env) {
3841 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
3843 env(offer(bob, USD(100),
XRP(100)));
3846 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3848 BEAST_EXPECT(
expectLine(env, bob, USD(1'100)));
3851 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3854 {{
XRP(10'000), USD(10'100)}},
3862 [&](
AMM& ammAlice,
Env& env) {
3863 env(
rate(gw, 1.25));
3869 env(offer(carol, EUR(100), GBP(100)));
3873 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
3875 BEAST_EXPECT(
expectLine(env, carol, GBP(29'875)));
3877 BEAST_EXPECT(
expectLine(env, carol, EUR(30'100)));
3880 {{GBP(1'000), EUR(1'100)}},
3886 [&](
AMM& amm,
Env& env) {
3887 env(
rate(gw, 1.001));
3889 env(offer(carol,
XRP(100), USD(55)));
3891 if (!features[fixAMMv1_1])
3901 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
3903 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
3914 BEAST_EXPECT(amm.expectBalances(
3916 STAmount{USD, UINT64_C(550'000000055), -9},
3925 STAmount{USD, 4'99999995, -8}}}}));
3929 STAmount(USD, UINT64_C(29'949'94999999494), -11));
3932 {{
XRP(1'000), USD(500)}},
3937 [&](
AMM& amm,
Env& env) {
3938 env(
rate(gw, 1.001));
3940 env(offer(carol,
XRP(10), USD(5.5)));
3942 if (!features[fixAMMv1_1])
3944 BEAST_EXPECT(amm.expectBalances(
3946 STAmount{USD, UINT64_C(505'050505050505), -12},
3952 BEAST_EXPECT(amm.expectBalances(
3954 STAmount{USD, UINT64_C(505'0505050505051), -13},
3959 {{
XRP(1'000), USD(500)}},
3965 [&](
AMM& ammAlice,
Env& env) {
3972 {GBP(2'000), EUR(2'000)},
3974 env(
rate(gw, 1.25));
3985 env(offer(carol, EUR(100), GBP(100)));
3987 if (!features[fixAMMv1_1])
3996 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
3997 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
4005 STAmount{EUR, UINT64_C(50'684828792831), -12},
4006 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
4018 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
4023 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
4035 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
4036 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
4044 STAmount{EUR, UINT64_C(27'06583722134028), -14},
4045 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
4057 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
4062 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
4065 BEAST_EXPECT(
expectLine(env, bob, GBP(2'010)));
4067 BEAST_EXPECT(
expectLine(env, ed, EUR(1'987.5)));
4069 {{GBP(1'000), EUR(1'100)}},
4082 [&](
AMM& ammAlice,
Env& env) {
4083 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
4084 env(
rate(gw, 1.25));
4086 env(pay(bob, carol, EUR(100)),
4092 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4094 BEAST_EXPECT(
expectLine(env, carol, EUR(30'080)));
4096 {{GBP(1'000), EUR(1'100)}},
4113 [&](
AMM& ammAlice,
Env& env) {
4116 auto const CAN = gw[
"CAN"];
4117 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
4118 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
4119 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
4120 env(trust(carol, USD(100)));
4121 env(
rate(gw, 1.25));
4123 env(offer(dan, CAN(200), GBP(200)));
4124 env(offer(ed, EUR(200), USD(200)));
4126 env(pay(bob, carol, USD(100)),
4127 path(~GBP, ~EUR, ~USD),
4132 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25), GBP(43.75)));
4134 GBP(10'125), EUR(10'000), ammAlice.
tokens()));
4135 BEAST_EXPECT(
expectLine(env, ed, EUR(300), USD(100)));
4136 BEAST_EXPECT(
expectLine(env, carol, USD(80)));
4138 {{GBP(10'000), EUR(10'125)}},
4145 [&](
AMM& ammAlice,
Env& env) {
4146 env(pay(alice, carol, USD(99.99)),
4151 env(pay(alice, carol, USD(100)),
4156 env(pay(alice, carol,
XRP(100)),
4167 {{
XRP(100), USD(100)}},
4174 Env env(*
this, features);
4175 auto const ETH = gw[
"ETH"];
4181 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4182 fund(env, gw, {carol, bob},
XRP(1'000), {USD(200)}, Fund::Acct);
4183 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4184 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4185 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4186 AMM xrp_usd(env, alice,
XRP(10'150), USD(10'200));
4187 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4188 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4189 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
4190 env(pay(bob, carol, USD(100)),
4191 path(~EUR, ~BTC, ~USD),
4193 path(~ETH, ~EUR, ~USD),
4195 if (!features[fixAMMv1_1])
4201 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
4204 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
4205 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4208 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4209 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4215 STAmount{USD, UINT64_C(10'126'06848287914), -11},
4222 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
4225 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
4226 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4229 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4230 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4236 STAmount{USD, UINT64_C(10'126'06848287943), -11},
4246 BEAST_EXPECT(xrp_eur.expectBalances(
4247 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
4249 EUR(10'000), BTC(10'200), eur_btc.
tokens()));
4251 BTC(10'100), USD(10'000), btc_usd.
tokens()));
4253 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4258 Env env(*
this, features);
4259 auto const ETH = gw[
"ETH"];
4265 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4266 fund(env, gw, {carol, bob},
XRP(1000), {USD(200)}, Fund::Acct);
4267 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4268 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4269 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4270 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4271 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4272 env(pay(bob, carol, USD(100)),
4273 path(~EUR, ~BTC, ~USD),
4274 path(~ETH, ~EUR, ~BTC, ~USD),
4276 if (!features[fixAMMv1_1])
4280 BEAST_EXPECT(xrp_eur.expectBalances(
4282 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
4285 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4286 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4289 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
4294 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
4297 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
4298 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4303 BEAST_EXPECT(xrp_eur.expectBalances(
4305 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
4308 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4309 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4312 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
4317 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
4320 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
4321 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4324 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4330 [&](
AMM& ammAlice,
Env& env) {
4332 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4333 env(trust(alice, EUR(200)));
4334 for (
int i = 0; i < 30; ++i)
4335 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4338 env(offer(alice, EUR(140),
XRP(100)));
4339 env(pay(bob, carol, USD(100)),
4343 if (!features[fixAMMv1_1])
4348 STAmount{USD, UINT64_C(9'970'089730807577), -12},
4353 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
4359 STAmount{USD, UINT64_C(9'970'089730807827), -12},
4364 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
4375 [&](
AMM& ammAlice,
Env& env) {
4377 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4378 env(trust(alice, EUR(200)));
4379 for (
int i = 0; i < 29; ++i)
4380 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4383 env(offer(alice, EUR(140),
XRP(100)));
4384 env(pay(bob, carol, USD(100)),
4390 if (!features[fixAMMv1_1])
4396 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4400 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4406 {{{
STAmount{EUR, UINT64_C(39'1858572), -7},
4417 Env env(*
this, features);
4418 fund(env, gw, {alice, carol, bob},
XRP(30'000), {USD(30'000)});
4419 env(offer(bob,
XRP(100), USD(100.001)));
4420 AMM ammAlice(env, alice,
XRP(10'000), USD(10'100));
4421 env(offer(carol, USD(100),
XRP(100)));
4422 if (!features[fixAMMv1_1])
4426 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4433 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
4439 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4446 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
4447 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4453 [&](
AMM& ammAlice,
Env& env) {
4457 env(pay(alice, carol, USD(1)),
4472 testcase(
"AMM Tokens");
4473 using namespace jtx;
4476 testAMM([&](
AMM& ammAlice,
Env& env) {
4477 auto const token1 = ammAlice.
lptIssue();
4484 env(offer(carol,
STAmount{token1, 5'000'000}, priceXRP));
4486 env(offer(alice, priceXRP,
STAmount{token1, 5'000'000}));
4494 ammAlice.
vote(carol, 1'000);
4496 ammAlice.
vote(carol, 0);
4499 env(ammAlice.
bid({.account = carol, .bidMin = 100}));
4520 testAMM([&](
AMM& ammAlice,
Env& env) {
4521 ammAlice.
deposit(carol, 1'000'000);
4522 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
4523 AMM ammAlice1(env, alice,
XRP(10'000), EUR(10'000));
4524 ammAlice1.deposit(carol, 1'000'000);
4525 auto const token1 = ammAlice.
lptIssue();
4526 auto const token2 = ammAlice1.lptIssue();
4546 testAMM([&](
AMM& ammAlice,
Env& env) {
4547 auto const token1 = ammAlice.
lptIssue();
4550 ammAlice.
deposit(carol, 1'000'000);
4556 env(pay(alice, carol,
STAmount{token1, 100}));
4566 env(pay(carol, alice,
STAmount{token1, 100}));
4578 testcase(
"Amendment");
4579 using namespace jtx;
4584 all - featureAMM - fixUniversalNumber};
4586 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
4588 Env env{*
this, feature};
4589 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
4607 using namespace jtx;
4609 testAMM([&](
AMM& ammAlice,
Env& env) {
4610 auto const info = env.
rpc(
4614 "{\"account\": \"" + to_string(ammAlice.
ammAccount()) +
4617 info[jss::result][jss::account_data][jss::Flags].asUInt();
4627 testcase(
"Rippling");
4628 using namespace jtx;
4644 auto const TSTA = A[
"TST"];
4645 auto const TSTB = B[
"TST"];
4654 env.
trust(TSTA(10'000), C);
4655 env.
trust(TSTB(10'000), C);
4656 env(pay(A, C, TSTA(10'000)));
4657 env(pay(B, C, TSTB(10'000)));
4658 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
4659 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
4666 env(pay(C, D,
STAmount{ammIss, 10}),
4668 path(amm.ammAccount()),
4677 testcase(
"AMMAndCLOB, offer quality change");
4678 using namespace jtx;
4679 auto const gw =
Account(
"gw");
4680 auto const TST = gw[
"TST"];
4681 auto const LP1 =
Account(
"LP1");
4682 auto const LP2 =
Account(
"LP2");
4684 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4685 Env env(*
this, features);
4686 env.
fund(
XRP(30'000'000'000), gw);
4687 env(offer(gw,
XRP(11'500'000'000), TST(1'000'000'000)));
4691 env(offer(LP1, TST(25),
XRPAmount(287'500'000)));
4696 env(offer(LP2, TST(25),
XRPAmount(287'500'000)));
4708 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
4714 lp2TakerGets = offer[
"taker_gets"].asString();
4715 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
4720 if (!features[fixAMMv1_1])
4724 STAmount{TST, UINT64_C(1'68737984885388), -14}),
4730 STAmount{TST, UINT64_C(1'68737976189735), -14}),
4739 BEAST_EXPECT(lp2TakerGets == offer[
"taker_gets"].asString());
4741 lp2TakerPays == offer[
"taker_pays"][
"value"].asString());
4748 testcase(
"Trading Fee");
4749 using namespace jtx;
4753 [&](
AMM& ammAlice,
Env& env) {
4755 ammAlice.
deposit(carol, USD(3'000));
4759 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4761 ammAlice.
vote(alice, 1'000);
4765 ammAlice.
deposit(carol, USD(3'000));
4767 carol,
IOUAmount{994'981155689671, -12}));
4768 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4770 ammAlice.
vote(alice, 0);
4776 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
4778 {{USD(1'000), EUR(1'000)}},
4786 [&](
AMM& ammAlice,
Env& env) {
4788 auto tokensFee = ammAlice.
deposit(
4789 carol, USD(1'000), std::nullopt,
STAmount{USD, 1, -1});
4792 ammAlice.
vote(alice, 0);
4794 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4797 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4798 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4808 [&](
AMM& ammAlice,
Env& env) {
4810 auto const tokensFee = ammAlice.
deposit(
4811 carol, USD(200), std::nullopt,
STAmount{USD, 2020, -6});
4814 ammAlice.
vote(alice, 0);
4816 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4819 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4820 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4829 [&](
AMM& ammAlice,
Env& env) {
4831 ammAlice.
deposit(carol, USD(3'000));
4834 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4836 ammAlice.
vote(alice, 1'000);
4843 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
4845 {{USD(1'000), EUR(1'000)}},
4852 [&](
AMM& ammAlice,
Env& env) {
4853 ammAlice.
deposit(carol, 1'000'000);
4854 auto const tokensFee = ammAlice.
withdraw(
4855 carol, USD(100), std::nullopt,
IOUAmount{520, 0});
4857 auto const balanceAfterWithdraw = [&]() {
4858 if (!features[fixAMMv1_1])
4859 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
4860 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
4862 BEAST_EXPECT(env.
balance(carol, USD) == balanceAfterWithdraw);
4864 auto const deposit = balanceAfterWithdraw - USD(29'000);
4865 ammAlice.
deposit(carol, deposit);
4867 ammAlice.
vote(alice, 0);
4869 auto const tokensNoFee = ammAlice.
withdraw(carol, deposit);
4870 if (!features[fixAMMv1_1])
4873 STAmount(USD, UINT64_C(30'443'43891402717), -11));
4877 STAmount(USD, UINT64_C(30'443'43891402716), -11));
4880 if (!features[fixAMMv1_1])
4882 tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4885 tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4886 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4895 [&](
AMM& ammAlice,
Env& env) {
4901 {USD(1'000), EUR(1'000)},
4904 BEAST_EXPECT(
expectLine(env, alice, EUR(28'990)));
4905 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4906 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4908 env(pay(carol, alice, EUR(10)),
4914 BEAST_EXPECT(
expectLine(env, alice, EUR(29'000)));
4915 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4916 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4919 ammAlice.
vote(alice, 1'000);
4922 env(pay(bob, carol, USD(10)),
4929 env, bob,
STAmount{EUR, UINT64_C(989'8989898989899), -13}));
4931 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4934 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
4937 {{USD(1'000), EUR(1'010)}},
4944 [&](
AMM& ammAlice,
Env& env) {
4946 env(offer(carol, EUR(10), USD(10)));
4948 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4949 BEAST_EXPECT(
expectLine(env, carol, EUR(30'010)));
4951 env(offer(carol, USD(10), EUR(10)));
4954 ammAlice.
vote(alice, 500);
4956 env(offer(carol, EUR(10), USD(10)));
4963 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
4967 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
4973 STAmount{EUR, UINT64_C(5'025125628140703), -15},
4974 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
4975 if (!features[fixAMMv1_1])
4978 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4979 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4985 STAmount{USD, UINT64_C(1'004'97487437186), -11},
4986 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4990 {{USD(1'000), EUR(1'010)}},
5000 Env env(*
this, features);
5005 {alice, bob, carol, ed},
5007 {USD(2'000), EUR(2'000)});
5008 env(offer(carol, EUR(5), USD(5)));
5009 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
5010 env(pay(bob, ed, USD(10)),
5014 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5015 if (!features[fixAMMv1_1])
5017 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5019 USD(1'000), EUR(1'005), ammAlice.
tokens()));
5024 env, bob,
STAmount(EUR, UINT64_C(1989'999999999999), -12)));
5027 STAmount(EUR, UINT64_C(1005'000000000001), -12),
5036 Env env(*
this, features);
5041 {alice, bob, carol, ed},
5043 {USD(2'000), EUR(2'000)});
5044 env(offer(carol, EUR(5), USD(5)));
5046 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 250);
5047 env(pay(bob, ed, USD(10)),
5051 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5052 if (!features[fixAMMv1_1])
5057 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
5060 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
5068 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
5071 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
5081 Env env(*
this, features);
5086 {alice, bob, carol, ed},
5088 {USD(2'000), EUR(2'000)});
5089 env(offer(carol, EUR(10), USD(10)));
5091 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5092 env(pay(bob, ed, USD(10)),
5096 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5097 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5099 USD(1'005), EUR(1'000), ammAlice.
tokens()));
5108 Env env(*
this, features);
5113 {alice, bob, carol, ed},
5115 {USD(2'000), EUR(2'000)});
5116 env(offer(carol, EUR(9), USD(9)));
5118 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5119 env(pay(bob, ed, USD(10)),
5123 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5125 env, bob,
STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
5128 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
5137 testcase(
"Adjusted Deposit/Withdraw Tokens");
5139 using namespace jtx;
5143 [&](
AMM& ammAlice,
Env& env) {
5151 Account const nataly(
"nataly");
5155 {bob, ed, paul, dan, chris, simon, ben, nataly},
5158 for (
int i = 0; i < 10; ++i)
5162 ammAlice.
deposit(simon, USD(0.1));
5164 ammAlice.
deposit(chris, USD(1));
5166 ammAlice.
deposit(dan, USD(10));
5168 ammAlice.
deposit(bob, USD(100));
5170 ammAlice.
deposit(carol, USD(1'000));
5172 ammAlice.
deposit(ed, USD(10'000));
5174 ammAlice.
deposit(paul, USD(100'000));
5176 ammAlice.
deposit(nataly, USD(1'000'000));
5182 if (!features[fixAMMv1_1])
5185 STAmount{USD, UINT64_C(10'000'0000000013), -10},
5190 BEAST_EXPECT(
expectLine(env, ben, USD(1'500'000)));
5191 BEAST_EXPECT(
expectLine(env, simon, USD(1'500'000)));
5192 BEAST_EXPECT(
expectLine(env, chris, USD(1'500'000)));
5193 BEAST_EXPECT(
expectLine(env, dan, USD(1'500'000)));
5194 if (!features[fixAMMv1_1])
5198 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
5200 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5201 BEAST_EXPECT(
expectLine(env, ed, USD(1'500'000)));
5202 BEAST_EXPECT(
expectLine(env, paul, USD(1'500'000)));
5203 if (!features[fixAMMv1_1])
5207 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
5212 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
5215 if (!features[fixAMMv1_1])
5219 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
5221 BEAST_EXPECT(
expectLine(env, alice, USD(30'000)));
5232 testAMM([&](
AMM& ammAlice,
Env& env) {
5240 Account const nataly(
"nataly");
5244 {bob, ed, paul, dan, chris, simon, ben, nataly},
5248 for (
int i = 0; i < 10; ++i)
5275 auto const xrpBalance = (
XRP(2'000'000) -
txfee(env, 20)).getText();
5293 testcase(
"Auto Delete");
5295 using namespace jtx;
5306 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5307 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5312 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5317 amm.withdrawAll(gw);
5318 BEAST_EXPECT(amm.ammExists());
5343 env(trust(alice,
STAmount{amm.lptIssue(), 10'000}),
5358 amm.expectBalances(
XRP(10'000), USD(10'000), amm.tokens()));
5359 BEAST_EXPECT(amm.expectTradingFee(1'000));
5360 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5364 amm.withdrawAll(alice);
5365 BEAST_EXPECT(!amm.ammExists());
5366 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5377 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5378 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5383 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5387 amm.withdrawAll(gw);
5388 BEAST_EXPECT(amm.ammExists());
5392 BEAST_EXPECT(amm.ammExists());
5394 amm.ammDelete(alice);
5395 BEAST_EXPECT(!amm.ammExists());
5396 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5406 testcase(
"Clawback");
5407 using namespace jtx;
5411 AMM amm(env, gw,
XRP(1'000), USD(1'000));
5419 using namespace jtx;
5420 testAMM([&](
AMM& amm,
Env& env) {
5421 amm.setClose(
false);
5422 auto const info = env.
rpc(
5426 "{\"account\": \"" + to_string(amm.ammAccount()) +
"\"}"));
5430 info[jss::result][jss::account_data][jss::AMMID]
5431 .asString() == to_string(amm.ammID()));
5437 amm.deposit(carol, 1'000);
5438 auto affected = env.
meta()->getJson(
5439 JsonOptions::none)[sfAffectedNodes.fieldName];
5443 for (
auto const& node : affected)
5445 if (node.isMember(sfModifiedNode.fieldName) &&
5446 node[sfModifiedNode.fieldName]
5447 [sfLedgerEntryType.fieldName]
5448 .asString() ==
"AccountRoot" &&
5449 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
5451 .asString() == to_string(amm.ammAccount()))
5453 found = node[sfModifiedNode.fieldName]
5454 [sfFinalFields.fieldName][jss::AMMID]
5455 .asString() == to_string(amm.ammID());
5459 BEAST_EXPECT(found);
5471 testcase(
"Offer/Strand Selection");
5472 using namespace jtx;
5475 auto const ETH = gw1[
"ETH"];
5476 auto const CAN = gw1[
"CAN"];
5482 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5483 fund(env, gw, {alice, carol, bob, ed},
XRP(2'000), {USD(2'000)});
5488 {alice, carol, bob, ed},
5489 {ETH(2'000), CAN(2'000)},
5491 env(
rate(gw, gwRate));
5492 env(
rate(gw1, gw1Rate));
5496 for (
auto const& rates :
5511 for (
auto i = 0; i < 3; ++i)
5513 Env env(*
this, features);
5514 prep(env, rates.first, rates.second);
5516 if (i == 0 || i == 2)
5522 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5523 env(pay(carol, bob, USD(100)),
5530 BEAST_EXPECT(amm->expectBalances(
5531 USD(1'000), ETH(1'000), amm->tokens()));
5533 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5534 q[i] = Quality(Amounts{
5535 ETH(2'000) - env.
balance(carol, ETH),
5536 env.
balance(bob, USD) - USD(2'000)});
5539 BEAST_EXPECT(q[0] > q[1]);
5541 BEAST_EXPECT(q[0] == q[2]);
5548 for (
auto i = 0; i < 3; ++i)
5550 Env env(*
this, features);
5551 prep(env, rates.first, rates.second);
5553 if (i == 0 || i == 2)
5559 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5560 env(offer(alice, USD(400), ETH(400)));
5565 BEAST_EXPECT(amm->expectBalances(
5566 USD(1'000), ETH(1'000), amm->tokens()));
5568 if (i == 0 || i == 2)
5577 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
5588 for (
auto i = 0; i < 3; ++i)
5590 Env env(*
this, features);
5591 prep(env, rates.first, rates.second);
5593 if (i == 0 || i == 2)
5599 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5600 env(pay(carol, bob, USD(100)),
5607 BEAST_EXPECT(!amm->expectBalances(
5608 USD(1'000), ETH(1'000), amm->tokens()));
5610 if (i == 2 && !features[fixAMMv1_1])
5612 if (rates.first == 1.5)
5614 if (!features[fixAMMv1_1])
5622 UINT64_C(378'6327949540823),
5626 UINT64_C(283'9745962155617),
5636 UINT64_C(378'6327949540813),
5640 UINT64_C(283'974596215561),
5645 if (!features[fixAMMv1_1])
5653 UINT64_C(325'299461620749),
5657 UINT64_C(243'9745962155617),
5667 UINT64_C(325'299461620748),
5671 UINT64_C(243'974596215561),
5677 if (rates.first == 1.5)
5685 ETH, UINT64_C(378'6327949540812), -13},
5688 UINT64_C(283'9745962155609),
5699 ETH, UINT64_C(325'2994616207479), -13},
5702 UINT64_C(243'9745962155609),
5706 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5707 q[i] = Quality(Amounts{
5708 ETH(2'000) - env.
balance(carol, ETH),
5709 env.
balance(bob, USD) - USD(2'000)});
5712 BEAST_EXPECT(q[1] > q[0]);
5714 BEAST_EXPECT(q[2] > q[1]);
5718 for (
auto i = 0; i < 3; ++i)
5720 Env env(*
this, features);
5721 prep(env, rates.first, rates.second);
5723 if (i == 0 || i == 2)
5729 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5730 env(offer(alice, USD(250), ETH(400)));
5735 BEAST_EXPECT(!amm->expectBalances(
5736 USD(1'000), ETH(1'000), amm->tokens()));
5742 if (rates.first == 1.5)
5744 if (!features[fixAMMv1_1])
5747 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
5754 USD, UINT64_C(40'5694150420947), -13},
5756 ETH, UINT64_C(64'91106406735152), -14},
5770 ETH, UINT64_C(335'0889359326475), -13},
5772 USD, UINT64_C(209'4305849579047), -13},
5779 if (!features[fixAMMv1_1])
5788 ETH, UINT64_C(335'0889359326485), -13},
5790 USD, UINT64_C(209'4305849579053), -13},
5803 ETH, UINT64_C(335'0889359326475), -13},
5805 USD, UINT64_C(209'4305849579047), -13},
5831 for (
auto i = 0; i < 3; ++i)
5833 Env env(*
this, features);
5834 prep(env, rates.first, rates.second);
5837 if (i == 0 || i == 2)
5845 amm.emplace(env, ed, ETH(1'000), USD(1'000));
5847 env(pay(carol, bob, USD(100)),
5853 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5855 if (i == 2 && !features[fixAMMv1_1])
5857 if (rates.first == 1.5)
5860 BEAST_EXPECT(amm->expectBalances(
5861 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
5867 BEAST_EXPECT(amm->expectBalances(
5869 ETH, UINT64_C(1'179'540094339627), -12},
5870 STAmount{USD, UINT64_C(847'7880529867501), -13},
5879 UINT64_C(343'3179205198749),
5883 UINT64_C(343'3179205198749),
5889 UINT64_C(362'2119470132499),
5893 UINT64_C(362'2119470132499),
5900 if (rates.first == 1.5)
5903 BEAST_EXPECT(amm->expectBalances(
5905 ETH, UINT64_C(1'176'660389557593), -12},
5911 BEAST_EXPECT(amm->expectBalances(
5912 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
5913 STAmount{USD, UINT64_C(847'7880529867501), -13},
5922 UINT64_C(343'3179205198749),
5926 UINT64_C(343'3179205198749),
5932 UINT64_C(362'2119470132499),
5936 UINT64_C(362'2119470132499),
5941 q[i] = Quality(Amounts{
5942 ETH(2'000) - env.
balance(carol, ETH),
5943 env.
balance(bob, USD) - USD(2'000)});
5945 BEAST_EXPECT(q[1] > q[0]);
5946 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5954 testcase(
"Fix Default Inner Object");
5955 using namespace jtx;
5966 Env env(*
this, features);
5967 fund(env, gw, {alice},
XRP(1'000), {USD(10)});
5973 {.tfee = tfee, .close = closeLedger});
5974 amm.deposit(alice, USD(10),
XRP(10));
5977 .
account = gw, .asset1Out = USD(1), .err =
ter(err2)});
5985 .
account = gw, .asset1Out = USD(2), .err =
ter(err4)});
5992 all - fixInnerObjTemplate,
6007 all - fixInnerObjTemplate,
6019 all - fixInnerObjTemplate,
6028 all - fixInnerObjTemplate,
6042 all - fixInnerObjTemplate,
6054 testcase(
"Fix changeSpotPriceQuality");
6055 using namespace jtx;
6058 SucceedShouldSucceedResize,
6070 auto const xrpIouAmounts10_100 =
6072 auto const iouXrpAmounts10_100 =
6077 {
"0.001519763260828713",
"1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
6078 {
"0.01099814367603737",
"1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
6079 {
"0.78",
"796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
6080 {
"105439.2955578965",
"49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
6081 {
"12408293.23445213",
"4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
6082 {
"1892611",
"0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
6083 {
"423028.8508101858",
"3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
6084 {
"44565388.41001027",
"73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
6085 {
"66831.68494832662",
"16", Quality{6346111134641742975}, 0, FailShouldSucceed},
6086 {
"675.9287302203422",
"1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
6087 {
"7047.112186735699",
"1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
6088 {
"840236.4402981238",
"47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
6089 {
"992715.618909774",
"189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
6090 {
"504636667521",
"185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
6091 {
"992706.7218636649",
"189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
6092 {
"1.068737911388205",
"127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
6093 {
"17932506.56880419",
"189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
6094 {
"1.066379294658174",
"128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
6095 {
"350131413924",
"1576879.110907892", Quality{6487411636539049449}, 650,
Fail},
6096 {
"422093460",
"2.731797662057464", Quality{6702911108534394924}, 1000,
Fail},
6097 {
"76128132223",
"367172.7148422662", Quality{6487263463413514240}, 548,
Fail},
6098 {
"132701839250",
"280703770.7695443", Quality{6273750681188885075}, 562,
Fail},
6099 {
"994165.7604612011",
"189551302411", Quality{5697835592690668727}, 815,
Fail},
6100 {
"45053.33303227917",
"86612695359", Quality{5625695218943638190}, 500,
Fail},
6101 {
"199649.077043865",
"14017933007", Quality{5766034667318524880}, 324,
Fail},
6102 {
"27751824831.70903",
"78896950", Quality{6272538159621630432}, 500,
Fail},
6103 {
"225.3731275781907",
"156431793648", Quality{5477818047604078924}, 989,
Fail},
6104 {
"199649.077043865",
"14017933007", Quality{5766036094462806309}, 324,
Fail},
6105 {
"3.590272027140361",
"20677643641", Quality{5406056147042156356}, 808,
Fail},
6106 {
"1.070884664490231",
"127604712776", Quality{5268620608623825741}, 293,
Fail},
6107 {
"3272.448829820197",
"6275124076", Quality{5625710328924117902}, 81,
Fail},
6108 {
"0.009059512633902926",
"7994028", Quality{5477511954775533172}, 1000,
Fail},
6109 {
"1",
"1.0", Quality{0}, 100,
Fail},
6110 {
"1.0",
"1", Quality{0}, 100,
Fail},
6111 {
"10",
"10.0", Quality{xrpIouAmounts10_100}, 100,
Fail},
6112 {
"10.0",
"10", Quality{iouXrpAmounts10_100}, 100,
Fail},
6113 {
"69864389131",
"287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
6114 {
"4328342973",
"12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
6115 {
"32347017",
"7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
6116 {
"61697206161",
"36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
6117 {
"1654524979",
"7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
6118 {
"88621.22277293179",
"5128418948", Quality{5766347291552869205}, 380, Succeed},
6119 {
"1892611",
"0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
6120 {
"4542.639373338766",
"24554809", Quality{5838994982188783710}, 0, Succeed},
6121 {
"5132932546",
"88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
6122 {
"78929964.1549083",
"1506494795", Quality{5986890029845558688}, 589, Succeed},
6123 {
"10096561906",
"44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
6124 {
"5092.219565514988",
"8768257694", Quality{5626349534958379008}, 503, Succeed},
6125 {
"1819778294",
"8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
6126 {
"6970462.633911943",
"57359281", Quality{6054087899185946624}, 850, Succeed},
6127 {
"3983448845",
"2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
6131 {
"771493171",
"1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
6135 boost::regex rx(
"^\\d+$");
6136 boost::smatch match;
6139 Env env(*
this, features);
6140 auto rules = env.
current()->rules();
6142 for (
auto const& t : tests)
6149 auto const& quality = std::get<Quality>(t);
6150 auto const tfee = std::get<std::uint16_t>(t);
6151 auto const status = std::get<Status>(t);
6152 auto const poolInIsXRP =
6153 boost::regex_search(std::get<0>(t), match, rx);
6154 auto const poolOutIsXRP =
6155 boost::regex_search(std::get<1>(t), match, rx);
6156 assert(!(poolInIsXRP && poolOutIsXRP));
6157 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
6158 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
6162 Amounts{poolIn, poolOut},
6169 if (status == SucceedShouldSucceedResize)
6171 if (!features[fixAMMv1_1])
6172 BEAST_EXPECT(Quality{*amounts} < quality);
6174 BEAST_EXPECT(Quality{*amounts} >= quality);
6176 else if (status == Succeed)
6178 if (!features[fixAMMv1_1])
6180 Quality{*amounts} >= quality ||
6182 Quality{*amounts}, quality,
Number{1, -7}));
6184 BEAST_EXPECT(Quality{*amounts} >= quality);
6186 else if (status == FailShouldSucceed)
6189 features[fixAMMv1_1] &&
6190 Quality{*amounts} >= quality);
6192 else if (status == SucceedShouldFail)
6195 !features[fixAMMv1_1] &&
6196 Quality{*amounts} < quality &&
6198 Quality{*amounts}, quality,
Number{1, -7}));
6207 if (status ==
Fail && quality != Quality{0})
6209 auto tinyOffer = [&]() {
6216 Amounts{poolIn, poolOut},
6220 else if (
isXRP(poolOut))
6225 Amounts{poolIn, poolOut},
6230 auto const takerPays = toAmount<STAmount>(
6235 Amounts{poolIn, poolOut}, takerPays, tfee)};
6237 BEAST_EXPECT(Quality(tinyOffer) < quality);
6239 else if (status == FailShouldSucceed)
6241 BEAST_EXPECT(!features[fixAMMv1_1]);
6243 else if (status == SucceedShouldFail)
6245 BEAST_EXPECT(features[fixAMMv1_1]);
6252 !strcmp(e.
what(),
"changeSpotPriceQuality failed"));
6254 !features[fixAMMv1_1] && status == FailShouldSucceed);
6263 BEAST_EXPECT(!res.has_value());
6270 using namespace jtx;
6272 testAMM([&](
AMM& ammAlice,
Env& env) {
6280 testAMM([&](
AMM& ammAlice,
Env& env) {
6288 testAMM([&](
AMM& ammAlice,
Env& env) {
6296 testAMM([&](
AMM& ammAlice,
Env& env) {
6299 .asset2Out =
XRP(100),
6305 testAMM([&](
AMM& ammAlice,
Env& env) {
6308 .asset2Out = BAD(100),
6314 testAMM([&](
AMM& ammAlice,
Env& env) {
6316 jv[jss::TransactionType] = jss::AMMWithdraw;
6318 jv[jss::Account] = alice.human();
6320 XRP(100).value().setJson(jv[jss::Amount]);
6321 USD(100).value().setJson(jv[jss::EPrice]);
6329 using namespace jtx;
6333 Account const gatehub{
"gatehub"};
6334 Account const bitstamp{
"bitstamp"};
6335 Account const trader{
"trader"};
6336 auto const usdGH = gatehub[
"USD"];
6337 auto const btcGH = gatehub[
"BTC"];
6338 auto const usdBIT = bitstamp[
"USD"];
6342 char const* testCase;
6343 double const poolUsdBIT;
6344 double const poolUsdGH;
6356 double const offer1BtcGH = 0.1;
6357 double const offer2BtcGH = 0.1;
6358 double const offer2UsdGH = 1;
6359 double const rateBIT = 0.0;
6360 double const rateGH = 0.0;
6365 for (
auto const& input : {
6367 .testCase =
"Test Fix Overflow Offer",
6370 .sendMaxUsdBIT{usdBIT(50)},
6371 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6374 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6375 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6376 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6377 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6378 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6379 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6380 .lpTokenBalance = {28'61817604250837, -14},
6388 .testCase =
"Overflow test {1, 100, 0.111}",
6391 .sendMaxUsdBIT{usdBIT(0.111)},
6392 .sendUsdGH{usdGH, 100},
6395 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6396 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6397 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6398 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6399 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6400 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6401 .lpTokenBalance{10, 0},
6402 .offer1BtcGH = 1e-5,
6404 .offer2UsdGH = 1e-5,
6409 .testCase =
"Overflow test {1, 100, 1.00}",
6412 .sendMaxUsdBIT{usdBIT(1.00)},
6413 .sendUsdGH{usdGH, 100},
6416 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6417 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6418 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6419 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6420 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6421 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6422 .lpTokenBalance{10, 0},
6423 .offer1BtcGH = 1e-5,
6425 .offer2UsdGH = 1e-5,
6430 .testCase =
"Overflow test {1, 100, 4.6432}",
6433 .sendMaxUsdBIT{usdBIT(4.6432)},
6434 .sendUsdGH{usdGH, 100},
6437 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6438 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6439 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6440 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6441 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6442 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6443 .lpTokenBalance{10, 0},
6444 .offer1BtcGH = 1e-5,
6446 .offer2UsdGH = 1e-5,
6451 .testCase =
"Overflow test {1, 100, 10}",
6454 .sendMaxUsdBIT{usdBIT(10)},
6455 .sendUsdGH{usdGH, 100},
6458 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6459 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6460 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6461 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6462 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6463 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6464 .lpTokenBalance{10, 0},
6465 .offer1BtcGH = 1e-5,
6467 .offer2UsdGH = 1e-5,
6472 .testCase =
"Overflow test {50, 100, 5.55}",
6475 .sendMaxUsdBIT{usdBIT(5.55)},
6476 .sendUsdGH{usdGH, 100},
6479 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6480 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6481 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6482 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6483 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6484 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6485 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6486 .offer1BtcGH = 1e-5,
6488 .offer2UsdGH = 1e-5,
6493 .testCase =
"Overflow test {50, 100, 50.00}",
6496 .sendMaxUsdBIT{usdBIT(50.00)},
6497 .sendUsdGH{usdGH, 100},
6498 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6499 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6500 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6501 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6502 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6503 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6504 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6505 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6506 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6507 .offer1BtcGH = 1e-5,
6509 .offer2UsdGH = 1e-5,
6514 .testCase =
"Overflow test {50, 100, 232.16}",
6517 .sendMaxUsdBIT{usdBIT(232.16)},
6518 .sendUsdGH{usdGH, 100},
6521 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6522 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6523 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6524 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6525 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6526 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6527 .lpTokenBalance{70'71067811865475, -14},
6528 .offer1BtcGH = 1e-5,
6530 .offer2UsdGH = 1e-5,
6535 .testCase =
"Overflow test {50, 100, 500}",
6538 .sendMaxUsdBIT{usdBIT(500)},
6539 .sendUsdGH{usdGH, 100},
6542 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6543 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6544 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6545 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6546 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6547 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6548 .lpTokenBalance{70'71067811865475, -14},
6549 .offer1BtcGH = 1e-5,
6551 .offer2UsdGH = 1e-5,
6557 testcase(input.testCase);
6558 for (
auto const& features :
6559 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
6561 Env env(*
this, features);
6563 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6566 if (input.rateGH != 0.0)
6567 env(
rate(gatehub, input.rateGH));
6568 if (input.rateBIT != 0.0)
6569 env(
rate(bitstamp, input.rateBIT));
6571 env(trust(trader, usdGH(10'000'000)));
6572 env(trust(trader, usdBIT(10'000'000)));
6573 env(trust(trader, btcGH(10'000'000)));
6576 env(pay(gatehub, trader, usdGH(100'000)));
6577 env(pay(gatehub, trader, btcGH(100'000)));
6578 env(pay(bitstamp, trader, usdBIT(100'000)));
6584 usdGH(input.poolUsdGH),
6585 usdBIT(input.poolUsdBIT)};
6589 amm.getLPTokensBalance();
6591 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6594 btcGH(input.offer2BtcGH),
6595 usdGH(input.offer2UsdGH)));
6598 env(pay(trader, trader, input.sendUsdGH),
6600 path(~btcGH, ~usdGH),
6605 auto const failUsdGH =
6606 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6607 auto const failUsdBIT =
6608 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6609 auto const goodUsdGH =
6610 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6611 auto const goodUsdBIT =
6612 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6613 if (!features[fixAMMOverflowOffer])
6615 BEAST_EXPECT(amm.expectBalances(
6616 failUsdGH, failUsdBIT, input.lpTokenBalance));
6620 BEAST_EXPECT(amm.expectBalances(
6621 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
6626 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6630 Number const sqrtPoolProduct =
6631 root2(goodUsdGH * goodUsdBIT);
6642 (sqrtPoolProduct +
Number{1, -14} >=
6643 input.lpTokenBalance));
6652 testcase(
"swapRounding");
6653 using namespace jtx;
6655 const STAmount xrpPool{
XRP, UINT64_C(51600'000981)};
6656 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
6658 const STAmount xrpBob{
XRP, UINT64_C(1092'878933)};
6660 USD, UINT64_C(3'988035892323031), -28};
6663 [&](
AMM& amm,
Env& env) {
6665 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(
XRP, USD);
6668 env.
fund(xrpBob, bob);
6669 env.
trust(USD(1'000'000), bob);
6670 env(pay(gw, bob, iouBob));
6673 env(offer(bob,
XRP(6300), USD(100'000)));
6678 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
6680 {{xrpPool, iouPool}},
6683 {jtx::supported_amendments() | fixAMMv1_1});
6689 testcase(
"AMM Offer Blocked By LOB");
6690 using namespace jtx;
6696 Env env(*
this, features);
6698 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6700 env(offer(alice,
XRP(1), USD(0.01)));
6703 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6707 env(offer(carol, USD(0.49),
XRP(1)));
6710 if (!features[fixAMMv1_1])
6712 BEAST_EXPECT(amm.expectBalances(
6713 XRP(200'000), USD(100'000), amm.tokens()));
6715 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6718 env, carol, 1, {{Amounts{USD(0.49),
XRP(1)}}}));
6722 BEAST_EXPECT(amm.expectBalances(
6723 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6725 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6734 Env env(*
this, features);
6736 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6740 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6743 env(offer(carol, USD(0.49),
XRP(1)));
6747 BEAST_EXPECT(amm.expectBalances(
6748 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6755 Env env(*
this, features);
6756 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6760 env(offer(bob, USD(1),
XRPAmount(500)));
6762 AMM amm(env, alice,
XRP(1'000), USD(500));
6763 env(offer(carol,
XRP(100), USD(55)));
6765 if (!features[fixAMMv1_1])
6768 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
6770 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6772 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
6776 BEAST_EXPECT(amm.expectBalances(
6778 STAmount{USD, UINT64_C(550'000000055), -9},
6786 STAmount{USD, 4'99999995, -8}}}}));
6788 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6795 Env env(*
this, features);
6796 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6798 AMM amm(env, alice,
XRP(1'000), USD(500));
6799 env(offer(carol,
XRP(100), USD(55)));
6801 BEAST_EXPECT(amm.expectBalances(
6803 STAmount{USD, UINT64_C(550'000000055), -9},
6817 using namespace jtx;
6821 Env env(*
this, features);
6827 {USD(1'000'000'000)});
6828 AMM amm(env, gw,
XRP(2), USD(1));
6829 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6830 amm.deposit(carol,
IOUAmount{1'000'000});
6831 amm.withdrawAll(alice);
6832 amm.withdrawAll(carol);
6834 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
6835 auto const lpTokenBalance =
6836 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6838 lpToken ==
"1414.213562373095" &&
6839 lpTokenBalance ==
"1414.213562373");
6840 if (!features[fixAMMv1_1])
6843 BEAST_EXPECT(amm.ammExists());
6847 amm.withdrawAll(gw);
6848 BEAST_EXPECT(!amm.ammExists());
6854 for (
auto const& lp : {gw, bob})
6856 Env env(*
this, features);
6857 auto const ABC = gw[
"ABC"];
6861 {alice, carol, bob},
6863 {USD(1'000'000'000), ABC(1'000'000'000'000)});
6864 AMM amm(env, lp, ABC(2'000'000), USD(1));
6865 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6866 amm.deposit(carol,
IOUAmount{1'000'000});
6867 amm.withdrawAll(alice);
6868 amm.withdrawAll(carol);
6870 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
6871 auto const lpTokenBalance =
6872 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6874 lpToken ==
"1414.213562373095" &&
6875 lpTokenBalance ==
"1414.213562373");
6876 if (!features[fixAMMv1_1])
6879 BEAST_EXPECT(amm.ammExists());
6883 amm.withdrawAll(lp);
6884 BEAST_EXPECT(!amm.ammExists());
6891 Env env(*
this, features);
6892 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)});
6893 AMM amm(env, gw,
XRP(10), USD(10));
6894 amm.deposit(alice, 1'000);
6897 BEAST_EXPECT(res && !res.value());
6900 BEAST_EXPECT(res && !res.value());
6904 Env env(*
this, features);
6905 fund(env, gw, {alice},
XRP(1'000), {USD(1'000), EUR(1'000)});
6906 AMM amm(env, gw, EUR(10), USD(10));
6907 amm.deposit(alice, 1'000);
6910 BEAST_EXPECT(res && !res.value());
6913 BEAST_EXPECT(res && !res.value());
6917 Env env(*
this, features);
6919 auto const YAN = gw1[
"YAN"];
6920 fund(env, gw, {gw1},
XRP(1'000), {USD(1'000)});
6921 fund(env, gw1, {gw},
XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
6922 AMM amm(env, gw1, YAN(10), USD(10));
6923 amm.deposit(gw, 1'000);
6926 BEAST_EXPECT(res && !res.value());
6928 BEAST_EXPECT(res && !res.value());
6935 testcase(
"test clawback from AMM account");
6936 using namespace jtx;
6939 Env env(*
this, features);
6942 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
6947 if (!features[featureAMMClawback])
6973 Issue usd(USD.issue().currency, amm.ammAccount());
6982 testcase(
"test AMMDeposit with frozen assets");
6983 using namespace jtx;
6990 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7000 Env env(*
this, features);
7001 testAMMDeposit(env, [&](
AMM& amm) {
7015 Env env(*
this, features);
7016 testAMMDeposit(env, [&](
AMM& amm) {
7027 if (features[featureAMMClawback])
7032 Env env(*
this, features);
7033 testAMMDeposit(env, [&](
AMM& amm) {
7048 Env env(*
this, features);
7049 testAMMDeposit(env, [&](
AMM& amm) {
7064 testcase(
"Fix Reserve Check On Withdrawal");
7065 using namespace jtx;
7070 auto test = [&](
auto&& cb) {
7071 Env env(*
this, features);
7072 auto const starting_xrp =
7073 reserve(env, 2) + env.
current()->fees().base * 5;
7074 env.
fund(starting_xrp, gw);
7075 env.
fund(starting_xrp, alice);
7076 env.
trust(USD(2'000), alice);
7078 env(pay(gw, alice, USD(2'000)));
7080 AMM amm(env, gw, EUR(1'000), USD(1'000));
7081 amm.deposit(alice, USD(1));
7086 test([&](
AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
7089 test([&](
AMM& amm) {
7092 .asset1Out = EUR(0.1),
7093 .asset2Out = USD(0.1),
7097 .asset1Out = USD(0.1),
7098 .asset2Out = EUR(0.1),
7103 test([&](
AMM& amm) {
7105 .
account = alice, .asset1Out = EUR(0.1), .err = err});
7114 testInvalidInstance();
7115 testInstanceCreate();
7116 testInvalidDeposit(all);
7117 testInvalidDeposit(all - featureAMMClawback);
7119 testInvalidWithdraw();
7121 testInvalidFeeVote();
7125 testBid(all - fixAMMv1_1);
7126 testInvalidAMMPayment();
7127 testBasicPaymentEngine(all);
7128 testBasicPaymentEngine(all - fixAMMv1_1);
7129 testBasicPaymentEngine(all - fixReducedOffersV2);
7130 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
7135 testAMMAndCLOB(all);
7136 testAMMAndCLOB(all - fixAMMv1_1);
7137 testTradingFee(all);
7138 testTradingFee(all - fixAMMv1_1);
7139 testAdjustedTokens(all);
7140 testAdjustedTokens(all - fixAMMv1_1);
7145 testSelection(all - fixAMMv1_1);
7146 testFixDefaultInnerObj();
7148 testFixOverflowOffer(all);
7149 testFixOverflowOffer(all - fixAMMv1_1);
7151 testFixChangeSpotPriceQuality(all);
7152 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
7153 testFixAMMOfferBlockedByLOB(all);
7154 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
7155 testLPTokenBalance(all);
7156 testLPTokenBalance(all - fixAMMv1_1);
7157 testAMMClawback(all);
7158 testAMMClawback(all - featureAMMClawback);
7159 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
7160 testAMMDepositWithFrozenAssets(all);
7161 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
7162 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
7163 testFixReserveCheckOnWithdrawal(all);
7164 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.