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;
1324 for (
const Number deltaLPTokens :
1325 {
Number{UINT64_C(100000'0000000009), -10},
1326 Number{UINT64_C(100000'0000000001), -10}})
1332 deltaLPTokens.
mantissa(), deltaLPTokens.exponent()};
1345 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1346 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1350 const Number fr = deltaLPTokens / 1e7;
1354 const Number deltaXRP = fr * 1e10;
1355 const Number deltaUSD = fr * 1e4;
1365 XRP(10'000) + depositXRP,
1366 USD(10'000) + depositUSD,
1490 STAmount{USD, UINT64_C(10'000'000001), -6},
1606 using namespace jtx;
1609 [&](
AMM& ammAlice,
Env& env) {
1619 [&](
AMM& ammAlice,
Env& env) {
1644 .asset1Out =
USD(100),
1773 for (
auto const& it : invalidOptions)
1784 ter(std::get<5>(it)));
1935 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
2040 [&](
AMM& ammAlice,
Env&) {
2052 [&](
AMM& ammAlice,
Env&) {
2123 using namespace jtx;
2291 [&](
AMM& ammAlice,
Env& env) {
2295 if (!env.
current()->rules().enabled(fixAMMv1_1))
2311 ammAlice.withdrawAll(
carol);
2317 {
all,
all - fixAMMv1_1});
2321 [&](AMM& ammAlice, Env& env) {
2322 ammAlice.deposit(carol, 1'000'000);
2324 carol, USD(0), std::nullopt, IOUAmount{520, 0});
2325 if (!env.current()->rules().enabled(fixAMMv1_1))
2327 ammAlice.expectBalances(
2328 XRPAmount(11'000'000'000),
2329 STAmount{USD, UINT64_C(9'372'781065088757), -12},
2330 IOUAmount{10'153'846'15384616, -8}) &&
2331 ammAlice.expectLPTokens(
2332 carol, IOUAmount{153'846'15384616, -8}));
2335 ammAlice.expectBalances(
2336 XRPAmount(11'000'000'000),
2337 STAmount{USD, UINT64_C(9'372'781065088769), -12},
2338 IOUAmount{10'153'846'15384616, -8}) &&
2339 ammAlice.expectLPTokens(
2340 carol, IOUAmount{153'846'15384616, -8}));
2345 {all, all - fixAMMv1_1});
2350 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
2351 env(
rate(gw, 1.25));
2354 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
2355 BEAST_EXPECT(ammAlice.expectBalances(
2356 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2357 BEAST_EXPECT(
expectLine(env, alice, USD(0)));
2358 BEAST_EXPECT(
expectLine(env, alice, BTC(0)));
2359 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
2361 ammAlice.deposit(carol, 10);
2362 BEAST_EXPECT(ammAlice.expectBalances(
2363 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
2364 BEAST_EXPECT(
expectLine(env, carol, USD(0)));
2365 BEAST_EXPECT(
expectLine(env, carol, BTC(0)));
2367 ammAlice.withdraw(carol, 10);
2368 BEAST_EXPECT(ammAlice.expectBalances(
2369 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2370 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
2371 BEAST_EXPECT(
expectLine(env, carol, USD(2'000)));
2372 BEAST_EXPECT(
expectLine(env, carol, BTC(0.05)));
2376 testAMM([&](AMM& ammAlice, Env&) {
2378 ammAlice.withdraw(alice, IOUAmount{1, -3});
2379 BEAST_EXPECT(ammAlice.expectBalances(
2380 XRPAmount{9'999'999'999},
2381 STAmount{USD, UINT64_C(9'999'999999), -6},
2382 IOUAmount{9'999'999'999, -3}));
2384 testAMM([&](AMM& ammAlice, Env&) {
2386 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
2387 BEAST_EXPECT(ammAlice.expectBalances(
2388 XRPAmount{9'999'999'999},
2390 IOUAmount{9'999'999'9995, -4}));
2392 testAMM([&](AMM& ammAlice, Env&) {
2394 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
2395 BEAST_EXPECT(ammAlice.expectBalances(
2397 STAmount{USD, UINT64_C(9'999'9999999999), -10},
2398 IOUAmount{9'999'999'99999995, -8}));
2403 testAMM([&](AMM& ammAlice, Env&) {
2404 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
2405 BEAST_EXPECT(ammAlice.expectBalances(
2406 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
2409 testAMM([&](AMM& ammAlice, Env&) {
2410 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
2411 BEAST_EXPECT(ammAlice.expectBalances(
2412 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
2415 testAMM([&](AMM& ammAlice, Env&) {
2416 ammAlice.withdraw(alice, IOUAmount{9'999'900},
XRP(0));
2417 BEAST_EXPECT(ammAlice.expectBalances(
2418 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2421 testAMM([&](AMM& ammAlice, Env&) {
2423 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
2424 BEAST_EXPECT(ammAlice.expectBalances(
2425 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
2428 testAMM([&](AMM& ammAlice, Env&) {
2429 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
2430 BEAST_EXPECT(ammAlice.expectBalances(
2431 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2438 testcase(
"Invalid Fee Vote");
2439 using namespace jtx;
2441 testAMM([&](
AMM& ammAlice,
Env& env) {
2492 testAMM([&](
AMM& ammAlice,
Env& env) {
2507 testcase(
"Fee Vote");
2508 using namespace jtx;
2511 testAMM([&](
AMM& ammAlice,
Env& env) {
2513 ammAlice.
vote({}, 1'000);
2519 auto vote = [&](
AMM& ammAlice,
2522 int fundUSD = 100'000,
2526 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2528 ammAlice.
vote(a, 50 * (i + 1));
2534 testAMM([&](
AMM& ammAlice,
Env& env) {
2535 for (
int i = 0; i < 7; ++i)
2536 vote(ammAlice, env, i, 10'000);
2542 testAMM([&](
AMM& ammAlice,
Env& env) {
2543 for (
int i = 0; i < 7; ++i)
2544 vote(ammAlice, env, i);
2547 ammAlice.
vote(a, 450);
2553 testAMM([&](
AMM& ammAlice,
Env& env) {
2554 for (
int i = 0; i < 7; ++i)
2555 vote(ammAlice, env, i);
2557 vote(ammAlice, env, 7, 100'000, 20'000'000);
2563 testAMM([&](
AMM& ammAlice,
Env& env) {
2564 for (
int i = 7; i > 0; --i)
2565 vote(ammAlice, env, i);
2567 vote(ammAlice, env, 0, 100'000, 20'000'000);
2574 testAMM([&](
AMM& ammAlice,
Env& env) {
2576 for (
int i = 0; i < 7; ++i)
2577 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2579 for (
int i = 0; i < 7; ++i)
2581 ammAlice.
deposit(carol, 10'000'000);
2582 ammAlice.
vote(carol, 1'000);
2591 testAMM([&](
AMM& ammAlice,
Env& env) {
2593 for (
int i = 0; i < 7; ++i)
2594 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2596 for (
int i = 0; i < 7; ++i)
2597 ammAlice.
withdraw(accounts[i], 9'000'000);
2598 ammAlice.
deposit(carol, 1'000);
2600 ammAlice.
vote(carol, 1'000);
2601 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2603 BEAST_EXPECT(info[i][jss::account] != carol.human());
2612 testcase(
"Invalid Bid");
2613 using namespace jtx;
2619 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2620 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2623 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2630 .bidMin = 1'000'000,
2638 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2639 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2642 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2649 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2655 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2659 amm.expectBalances(
XRP(1'000), USD(1'000),
IOUAmount{1}));
2662 BEAST_EXPECT(
Number{amm.getLPTokensBalance(gw)} == 1);
2674 testAMM([&](
AMM& ammAlice,
Env& env) {
2679 .flags = tfWithdrawAll,
2681 ter(temINVALID_FLAG));
2683 ammAlice.
deposit(carol, 1'000'000);
2685 for (
auto bid : {0, -100})
2691 ter(temBAD_AMOUNT));
2696 ter(temBAD_AMOUNT));
2705 ter(tecAMM_INVALID_TOKENS));
2715 ter(terNO_ACCOUNT));
2718 Account
const dan(
"dan");
2719 env.
fund(XRP(1'000), dan);
2724 ter(tecAMM_INVALID_TOKENS));
2728 ter(tecAMM_INVALID_TOKENS));
2734 .authAccounts = {bob},
2736 ter(terNO_ACCOUNT));
2742 .assets = {{USD, GBP}},
2749 .bidMax = STAmount{USD, 100},
2751 ter(temBAD_AMM_TOKENS));
2754 .bidMin = STAmount{USD, 100},
2756 ter(temBAD_AMM_TOKENS));
2760 testAMM([&](AMM& ammAlice, Env& env) {
2761 ammAlice.withdrawAll(alice);
2770 testAMM([&](AMM& ammAlice, Env& env) {
2772 Account bill(
"bill");
2773 Account scott(
"scott");
2774 Account james(
"james");
2775 env.fund(
XRP(1'000), bob, ed, bill, scott, james);
2777 ammAlice.deposit(carol, 1'000'000);
2781 .authAccounts = {bob, ed, bill, scott, james},
2787 testAMM([&](AMM& ammAlice, Env& env) {
2788 fund(env, gw, {bob},
XRP(1'000), {USD(100)}, Fund::Acct);
2789 ammAlice.deposit(carol, 1'000'000);
2790 ammAlice.deposit(bob, 10);
2793 .bidMin = 1'000'001,
2795 ter(tecAMM_INVALID_TOKENS));
2798 .bidMax = 1'000'001,
2800 ter(tecAMM_INVALID_TOKENS));
2805 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2810 ter(tecAMM_INVALID_TOKENS));
2816 fund(env, gw, {alice, bob},
XRP(1'000), {USD(1'000)});
2818 auto const lpIssue =
amm.lptIssue();
2819 env.trust(STAmount{lpIssue, 100}, alice);
2820 env.trust(STAmount{lpIssue, 50}, bob);
2821 env(
pay(gw, alice, STAmount{lpIssue, 100}));
2822 env(
pay(gw, bob, STAmount{lpIssue, 50}));
2823 env(
amm.bid({.account = alice, .bidMin = 100}));
2830 ter(tecAMM_FAILED));
2838 using namespace jtx;
2845 [&](
AMM& ammAlice,
Env& env) {
2846 ammAlice.
deposit(carol, 1'000'000);
2847 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2860 [&](
AMM& ammAlice,
Env& env) {
2861 ammAlice.
deposit(carol, 1'000'000);
2864 {.account = carol, .bidMin = 110, .bidMax = 110}));
2870 {.account = alice, .bidMin = 180, .bidMax = 200}));
2873 XRP(11'000), USD(11'000),
IOUAmount{10'999'814'5, -1}));
2882 [&](
AMM& ammAlice,
Env& env) {
2883 ammAlice.
deposit(carol, 1'000'000);
2885 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2888 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2889 ammAlice.
deposit(bob, 1'000'000);
2891 env(ammAlice.
bid({.account = bob}));
2902 env(ammAlice.
bid({.account = carol, .bidMax = 600}));
2916 {.account = carol, .bidMin = 100, .bidMax = 600}));
2927 [&](
AMM& ammAlice,
Env& env) {
2928 ammAlice.
deposit(carol, 1'000'000);
2930 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2931 ammAlice.
deposit(bob, 1'000'000);
2936 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2940 env(ammAlice.
bid({.account = bob}));
2946 env(ammAlice.
bid({.account = carol}));
2952 env(ammAlice.
bid({.account = bob}));
2958 0, std::nullopt,
IOUAmount{127'33875, -5}));
2961 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2966 XRP(12'000), USD(12'000),
IOUAmount{11'999'678'91, -2}));
2978 [&](
AMM& ammAlice,
Env& env) {
2981 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
2982 ammAlice.
deposit(bob, 1'000'000);
2983 ammAlice.
deposit(ed, 1'000'000);
2984 ammAlice.
deposit(carol, 500'000);
2985 ammAlice.
deposit(dan, 500'000);
2990 .authAccounts = {bob, ed},
2992 auto const slotPrice =
IOUAmount{5'200};
2993 ammTokens -= slotPrice;
2996 XRP(13'000), USD(13'000), ammTokens));
2998 for (
int i = 0; i < 10; ++i)
3000 auto tokens = ammAlice.
deposit(carol, USD(100));
3001 ammAlice.
withdraw(carol, tokens, USD(0));
3002 tokens = ammAlice.
deposit(bob, USD(100));
3003 ammAlice.
withdraw(bob, tokens, USD(0));
3004 tokens = ammAlice.
deposit(ed, USD(100));
3005 ammAlice.
withdraw(ed, tokens, USD(0));
3008 if (!features[fixAMMv1_1])
3012 STAmount(USD, UINT64_C(29'499'00572620545), -11));
3015 STAmount(USD, UINT64_C(18'999'00572616195), -11));
3018 STAmount(USD, UINT64_C(18'999'00572611841), -11));
3022 STAmount(USD, UINT64_C(13'002'98282151419), -11),
3029 STAmount(USD, UINT64_C(29'499'00572620544), -11));
3032 STAmount(USD, UINT64_C(18'999'00572616194), -11));
3035 STAmount(USD, UINT64_C(18'999'0057261184), -10));
3039 STAmount(USD, UINT64_C(13'002'98282151422), -11),
3044 for (
int i = 0; i < 10; ++i)
3046 auto const tokens = ammAlice.
deposit(dan, USD(100));
3047 ammAlice.
withdraw(dan, tokens, USD(0));
3052 if (!features[fixAMMv1_1])
3056 STAmount(USD, UINT64_C(19'490'056722744), -9));
3060 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3063 ammAlice.
deposit(carol, USD(100));
3067 STAmount{USD, UINT64_C(13'112'92609877019), -11},
3069 env(pay(carol, bob, USD(100)),
3077 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3084 STAmount(USD, UINT64_C(19'490'05672274399), -11));
3088 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3091 ammAlice.
deposit(carol, USD(100));
3095 STAmount{USD, UINT64_C(13'112'92609877023), -11},
3097 env(pay(carol, bob, USD(100)),
3105 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3114 if (!features[fixAMMv1_1])
3118 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3125 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3132 if (!features[fixAMMv1_1])
3135 STAmount(USD, UINT64_C(29'399'00572620545), -11));
3139 STAmount(USD, UINT64_C(29'399'00572620544), -11));
3141 for (
int i = 0; i < 10; ++i)
3143 auto const tokens = ammAlice.
deposit(carol, USD(100));
3144 ammAlice.
withdraw(carol, tokens, USD(0));
3148 if (!features[fixAMMv1_1])
3152 STAmount(USD, UINT64_C(29'389'06197177128), -11));
3155 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3162 STAmount(USD, UINT64_C(29'389'06197177124), -11));
3165 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3173 if (!features[fixAMMv1_1])
3177 STAmount{USD, UINT64_C(13'023'98038490681), -11},
3184 STAmount{USD, UINT64_C(13'023'98038490689), -11},
3195 [&](AMM& ammAlice, Env& env) {
3198 Number{STAmount::cMinValue, STAmount::cMinOffset};
3200 {.account = alice, .bidMin = IOUAmount{tiny}}));
3203 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
3205 BEAST_EXPECT(ammAlice.expectBalances(
3206 XRP(10'000), USD(10'000), ammAlice.tokens()));
3211 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
3214 BEAST_EXPECT(ammAlice.expectAuctionSlot(
3215 0, 0, IOUAmount{tiny * Number{105, -2}}));
3218 BEAST_EXPECT(ammAlice.expectBalances(
3219 XRP(10'000), USD(10'000), ammAlice.tokens()));
3228 [&](AMM& ammAlice, Env& env) {
3231 .bidMin = IOUAmount{100},
3232 .authAccounts = {carol},
3234 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
3235 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
3236 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3239 fund(env, {bob, dan},
XRP(1'000));
3242 .bidMin = IOUAmount{100},
3243 .authAccounts = {bob, dan},
3245 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3254 Env env(*
this, features);
3255 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3256 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3257 auto const lpIssue =
amm.lptIssue();
3258 env.trust(STAmount{lpIssue, 500}, alice);
3259 env.trust(STAmount{lpIssue, 50}, bob);
3260 env(
pay(gw, alice, STAmount{lpIssue, 500}));
3261 env(
pay(gw, bob, STAmount{lpIssue, 50}));
3263 env(
amm.bid({.account = alice, .bidMin = 500}));
3264 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3265 BEAST_EXPECT(
expectLine(env, alice, STAmount{lpIssue, 0}));
3268 env(
pay(alice, bob, USD(10)), path(~USD), sendmax(
XRP(11)));
3269 BEAST_EXPECT(
amm.expectBalances(
3270 XRPAmount{1'010'010'011},
3272 IOUAmount{1'004'487'562112089, -9}));
3274 env(
pay(bob, alice,
XRP(10)), path(~XRP), sendmax(USD(11)));
3275 if (!features[fixAMMv1_1])
3277 BEAST_EXPECT(
amm.expectBalances(
3278 XRPAmount{1'000'010'011},
3279 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3280 IOUAmount{1'004'487'562112089, -9}));
3284 BEAST_EXPECT(
amm.expectBalances(
3285 XRPAmount{1'000'010'011},
3286 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3287 IOUAmount{1'004'487'562112089, -9}));
3293 Env env(*
this, features);
3294 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3295 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3299 auto jtx = env.jt(tx, seq(1), fee(10));
3300 env.app().config().features.erase(featureAMM);
3301 PreflightContext pfctx(
3304 env.current()->rules(),
3307 auto pf = AMMBid::preflight(pfctx);
3308 BEAST_EXPECT(pf == temDISABLED);
3309 env.app().config().features.insert(featureAMM);
3313 auto jtx = env.jt(tx, seq(1), fee(10));
3314 jtx.jv[
"TxnSignature"] =
"deadbeef";
3315 jtx.stx = env.ust(jtx);
3316 PreflightContext pfctx(
3319 env.current()->rules(),
3322 auto pf = AMMBid::preflight(pfctx);
3323 BEAST_EXPECT(pf != tesSUCCESS);
3327 auto jtx = env.jt(tx, seq(1), fee(10));
3328 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3329 jtx.jv[
"Asset2"].removeMember(
"issuer");
3330 jtx.stx = env.ust(jtx);
3331 PreflightContext pfctx(
3334 env.current()->rules(),
3337 auto pf = AMMBid::preflight(pfctx);
3338 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3346 testcase(
"Invalid AMM Payment");
3347 using namespace jtx;
3349 using namespace std::literals::chrono_literals;
3353 for (
auto const& acct : {gw, alice})
3357 fund(env, gw, {alice, carol},
XRP(1'000), {USD(100)});
3359 AMM ammAlice(env, acct,
XRP(10), USD(10));
3361 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3364 env(pay(carol, ammAlice.ammAccount(),
XRP(300)),
3367 env(pay(carol, ammAlice.ammAccount(), USD(10)),
3372 fund(env, gw, {alice, carol},
XRP(10'000'000), {USD(10'000)});
3374 AMM ammAlice(env, acct,
XRP(1'000'000), USD(100));
3376 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3379 env(pay(carol, ammAlice.ammAccount(),
XRP(1'000'000)),
3385 testAMM([&](
AMM& ammAlice,
Env& env) {
3395 testAMM([&](
AMM& ammAlice,
Env& env) {
3396 auto const pk = carol.pk();
3397 auto const settleDelay = 100s;
3399 env.
current()->info().parentCloseTime + 200s;
3411 testAMM([&](
AMM& ammAlice,
Env& env) {
3418 [&](
AMM& ammAlice,
Env& env) {
3420 env(pay(alice, carol, USD(100)),
3424 env(pay(alice, carol,
XRP(100)),
3431 STAmount{USD, UINT64_C(99'999999999), -9}),
3437 STAmount{USD, UINT64_C(999'99999999), -8}),
3446 env(pay(alice, carol, USD(99.99)),
3455 {{
XRP(100), USD(100)}});
3458 testAMM([&](
AMM& ammAlice,
Env& env) {
3461 env(pay(alice, carol, USD(1)),
3466 env(pay(alice, carol,
XRP(1)),
3474 testAMM([&](
AMM& ammAlice,
Env& env) {
3480 env(pay(alice, carol, USD(1)),
3485 env(pay(alice, carol,
XRP(1)),
3493 testAMM([&](
AMM& ammAlice,
Env& env) {
3497 env(pay(alice, carol,
XRP(1)),
3508 testcase(
"Basic Payment");
3509 using namespace jtx;
3514 [&](
AMM& ammAlice,
Env& env) {
3515 env.
fund(jtx::XRP(30'000), bob);
3517 env(pay(bob, carol, USD(100)),
3523 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3525 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3528 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3530 {{
XRP(10'000), USD(10'100)}},
3537 [&](
AMM& ammAlice,
Env& env) {
3538 env.
fund(jtx::XRP(30'000), bob);
3540 env(pay(bob, carol, USD(100)),
sendmax(
XRP(100)));
3543 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3545 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3548 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3550 {{
XRP(10'000), USD(10'100)}},
3558 [&](
AMM& ammAlice,
Env& env) {
3559 env.
fund(jtx::XRP(30'000), bob);
3564 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3566 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3569 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3571 {{
XRP(10'000), USD(10'100)}},
3578 [&](
AMM& ammAlice,
Env& env) {
3579 env.
fund(jtx::XRP(30'000), bob);
3583 env(pay(bob, carol, USD(100)),
3590 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3592 BEAST_EXPECT(
expectLine(env, carol, USD(30'010)));
3596 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3600 env(pay(bob, carol, USD(100)),
3608 {{
XRP(10'000), USD(10'010)}},
3615 [&](
AMM& ammAlice,
Env& env) {
3618 env.
fund(jtx::XRP(30'000), bob);
3623 env(pay(bob, carol, USD(100)),
3630 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3635 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
3637 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3639 {{
XRP(10'000), USD(10'010)}},
3646 [&](
AMM& ammAlice,
Env& env) {
3647 env.
fund(jtx::XRP(30'000), bob);
3649 env(pay(bob, carol, USD(100)),
3655 {{
XRP(10'000), USD(10'000)}},
3665 Env env(*
this, features);
3667 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3671 auto ammEUR_XRP =
AMM(env, alice,
XRP(10'000), EUR(10'000));
3672 auto ammUSD_EUR =
AMM(env, alice, EUR(10'000), USD(10'000));
3675 env(pay(bob, carol, USD(100)),
3680 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3682 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3683 ammEUR_XRP.tokens()));
3684 if (!features[fixAMMv1_1])
3686 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3687 STAmount(USD, UINT64_C(9'970'097277662122), -12),
3688 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3689 ammUSD_EUR.tokens()));
3692 Amounts
const expectedAmounts =
3693 env.
closed()->rules().enabled(fixReducedOffersV2)
3694 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233787816), -14)}
3697 STAmount(USD, UINT64_C(29'90272233787818), -14)};
3699 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3703 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3704 STAmount(USD, UINT64_C(9'970'097277662172), -12),
3705 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3706 ammUSD_EUR.tokens()));
3709 Amounts
const expectedAmounts =
3710 env.
closed()->rules().enabled(fixReducedOffersV2)
3711 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233782839), -14)}
3714 STAmount(USD, UINT64_C(29'90272233782840), -14)};
3716 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3732 [&](
AMM& ammAlice,
Env& env) {
3735 env.
trust(EUR(2'000), alice);
3737 env(pay(gw, alice, EUR(1'000)));
3742 env(pay(bob, carol, USD(100)),
3749 STAmount(USD, UINT64_C(9'950'01249687578), -11),
3757 STAmount(EUR, UINT64_C(49'98750312422), -11)},
3759 STAmount(EUR, UINT64_C(49'98750312422), -11),
3760 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
3765 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3782 [&](
AMM& ammAlice,
Env& env) {
3783 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3787 env(pay(alice, carol, USD(200)),
3791 if (!features[fixAMMv1_1])
3794 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3796 BEAST_EXPECT(
expectLine(env, carol, USD(30'200)));
3802 STAmount(USD, UINT64_C(10'000'00000000001), -11),
3807 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
3815 ammCrtFee(env) -
txfee(env, 1)));
3818 {{
XRP(10'000), USD(10'100)}},
3827 Env env(*
this, features);
3828 fund(env, gw, {alice, bob, carol},
XRP(20'000), {USD(2'000)});
3830 AMM ammAlice(env, alice,
XRP(1'000), USD(1'050));
3831 env(pay(alice, carol, USD(200)),
3835 XRP(1'050), USD(1'000), ammAlice.
tokens()));
3836 BEAST_EXPECT(
expectLine(env, carol, USD(2'200)));
3842 [&](
AMM& ammAlice,
Env& env) {
3843 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
3845 env(offer(bob, USD(100),
XRP(100)));
3848 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3850 BEAST_EXPECT(
expectLine(env, bob, USD(1'100)));
3853 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3856 {{
XRP(10'000), USD(10'100)}},
3864 [&](
AMM& ammAlice,
Env& env) {
3865 env(
rate(gw, 1.25));
3871 env(offer(carol, EUR(100), GBP(100)));
3875 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
3877 BEAST_EXPECT(
expectLine(env, carol, GBP(29'875)));
3879 BEAST_EXPECT(
expectLine(env, carol, EUR(30'100)));
3882 {{GBP(1'000), EUR(1'100)}},
3888 [&](
AMM& amm,
Env& env) {
3889 env(
rate(gw, 1.001));
3891 env(offer(carol,
XRP(100), USD(55)));
3893 if (!features[fixAMMv1_1])
3903 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
3905 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
3916 BEAST_EXPECT(amm.expectBalances(
3918 STAmount{USD, UINT64_C(550'000000055), -9},
3927 STAmount{USD, 4'99999995, -8}}}}));
3931 STAmount(USD, UINT64_C(29'949'94999999494), -11));
3934 {{
XRP(1'000), USD(500)}},
3939 [&](
AMM& amm,
Env& env) {
3940 env(
rate(gw, 1.001));
3942 env(offer(carol,
XRP(10), USD(5.5)));
3944 if (!features[fixAMMv1_1])
3946 BEAST_EXPECT(amm.expectBalances(
3948 STAmount{USD, UINT64_C(505'050505050505), -12},
3954 BEAST_EXPECT(amm.expectBalances(
3956 STAmount{USD, UINT64_C(505'0505050505051), -13},
3961 {{
XRP(1'000), USD(500)}},
3967 [&](
AMM& ammAlice,
Env& env) {
3974 {GBP(2'000), EUR(2'000)},
3976 env(
rate(gw, 1.25));
3987 env(offer(carol, EUR(100), GBP(100)));
3989 if (!features[fixAMMv1_1])
3998 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
3999 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
4007 STAmount{EUR, UINT64_C(50'684828792831), -12},
4008 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
4020 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
4025 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
4037 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
4038 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
4046 STAmount{EUR, UINT64_C(27'06583722134028), -14},
4047 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
4059 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
4064 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
4067 BEAST_EXPECT(
expectLine(env, bob, GBP(2'010)));
4069 BEAST_EXPECT(
expectLine(env, ed, EUR(1'987.5)));
4071 {{GBP(1'000), EUR(1'100)}},
4084 [&](
AMM& ammAlice,
Env& env) {
4085 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
4086 env(
rate(gw, 1.25));
4088 env(pay(bob, carol, EUR(100)),
4094 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4096 BEAST_EXPECT(
expectLine(env, carol, EUR(30'080)));
4098 {{GBP(1'000), EUR(1'100)}},
4115 [&](
AMM& ammAlice,
Env& env) {
4118 auto const CAN = gw[
"CAN"];
4119 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
4120 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
4121 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
4122 env(trust(carol, USD(100)));
4123 env(
rate(gw, 1.25));
4125 env(offer(dan, CAN(200), GBP(200)));
4126 env(offer(ed, EUR(200), USD(200)));
4128 env(pay(bob, carol, USD(100)),
4129 path(~GBP, ~EUR, ~USD),
4134 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25), GBP(43.75)));
4136 GBP(10'125), EUR(10'000), ammAlice.
tokens()));
4137 BEAST_EXPECT(
expectLine(env, ed, EUR(300), USD(100)));
4138 BEAST_EXPECT(
expectLine(env, carol, USD(80)));
4140 {{GBP(10'000), EUR(10'125)}},
4147 [&](
AMM& ammAlice,
Env& env) {
4148 env(pay(alice, carol, USD(99.99)),
4153 env(pay(alice, carol, USD(100)),
4158 env(pay(alice, carol,
XRP(100)),
4169 {{
XRP(100), USD(100)}},
4176 Env env(*
this, features);
4177 auto const ETH = gw[
"ETH"];
4183 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4184 fund(env, gw, {carol, bob},
XRP(1'000), {USD(200)}, Fund::Acct);
4185 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4186 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4187 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4188 AMM xrp_usd(env, alice,
XRP(10'150), USD(10'200));
4189 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4190 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4191 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
4192 env(pay(bob, carol, USD(100)),
4193 path(~EUR, ~BTC, ~USD),
4195 path(~ETH, ~EUR, ~USD),
4197 if (!features[fixAMMv1_1])
4203 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
4206 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
4207 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4210 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4211 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4217 STAmount{USD, UINT64_C(10'126'06848287914), -11},
4224 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
4227 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
4228 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4231 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4232 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4238 STAmount{USD, UINT64_C(10'126'06848287943), -11},
4248 BEAST_EXPECT(xrp_eur.expectBalances(
4249 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
4251 EUR(10'000), BTC(10'200), eur_btc.
tokens()));
4253 BTC(10'100), USD(10'000), btc_usd.
tokens()));
4255 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4260 Env env(*
this, features);
4261 auto const ETH = gw[
"ETH"];
4267 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4268 fund(env, gw, {carol, bob},
XRP(1000), {USD(200)}, Fund::Acct);
4269 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4270 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4271 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4272 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4273 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4274 env(pay(bob, carol, USD(100)),
4275 path(~EUR, ~BTC, ~USD),
4276 path(~ETH, ~EUR, ~BTC, ~USD),
4278 if (!features[fixAMMv1_1])
4282 BEAST_EXPECT(xrp_eur.expectBalances(
4284 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
4287 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4288 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4291 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
4296 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
4299 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
4300 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4305 BEAST_EXPECT(xrp_eur.expectBalances(
4307 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
4310 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4311 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4314 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
4319 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
4322 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
4323 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4326 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4332 [&](
AMM& ammAlice,
Env& env) {
4334 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4335 env(trust(alice, EUR(200)));
4336 for (
int i = 0; i < 30; ++i)
4337 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4340 env(offer(alice, EUR(140),
XRP(100)));
4341 env(pay(bob, carol, USD(100)),
4345 if (!features[fixAMMv1_1])
4350 STAmount{USD, UINT64_C(9'970'089730807577), -12},
4355 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
4361 STAmount{USD, UINT64_C(9'970'089730807827), -12},
4366 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
4377 [&](
AMM& ammAlice,
Env& env) {
4379 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4380 env(trust(alice, EUR(200)));
4381 for (
int i = 0; i < 29; ++i)
4382 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4385 env(offer(alice, EUR(140),
XRP(100)));
4386 env(pay(bob, carol, USD(100)),
4392 if (!features[fixAMMv1_1])
4398 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4402 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4408 {{{
STAmount{EUR, UINT64_C(39'1858572), -7},
4419 Env env(*
this, features);
4420 fund(env, gw, {alice, carol, bob},
XRP(30'000), {USD(30'000)});
4421 env(offer(bob,
XRP(100), USD(100.001)));
4422 AMM ammAlice(env, alice,
XRP(10'000), USD(10'100));
4423 env(offer(carol, USD(100),
XRP(100)));
4424 if (!features[fixAMMv1_1])
4428 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4435 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
4441 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4448 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
4449 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4455 [&](
AMM& ammAlice,
Env& env) {
4459 env(pay(alice, carol, USD(1)),
4474 testcase(
"AMM Tokens");
4475 using namespace jtx;
4478 testAMM([&](
AMM& ammAlice,
Env& env) {
4479 auto const token1 = ammAlice.
lptIssue();
4486 env(offer(carol,
STAmount{token1, 5'000'000}, priceXRP));
4488 env(offer(alice, priceXRP,
STAmount{token1, 5'000'000}));
4496 ammAlice.
vote(carol, 1'000);
4498 ammAlice.
vote(carol, 0);
4501 env(ammAlice.
bid({.account = carol, .bidMin = 100}));
4522 testAMM([&](
AMM& ammAlice,
Env& env) {
4523 ammAlice.
deposit(carol, 1'000'000);
4524 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
4525 AMM ammAlice1(env, alice,
XRP(10'000), EUR(10'000));
4526 ammAlice1.deposit(carol, 1'000'000);
4527 auto const token1 = ammAlice.
lptIssue();
4528 auto const token2 = ammAlice1.lptIssue();
4548 testAMM([&](
AMM& ammAlice,
Env& env) {
4549 auto const token1 = ammAlice.
lptIssue();
4552 ammAlice.
deposit(carol, 1'000'000);
4558 env(pay(alice, carol,
STAmount{token1, 100}));
4568 env(pay(carol, alice,
STAmount{token1, 100}));
4580 testcase(
"Amendment");
4581 using namespace jtx;
4586 all - featureAMM - fixUniversalNumber};
4588 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
4590 Env env{*
this, feature};
4591 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
4609 using namespace jtx;
4611 testAMM([&](
AMM& ammAlice,
Env& env) {
4612 auto const info = env.
rpc(
4616 "{\"account\": \"" + to_string(ammAlice.
ammAccount()) +
4619 info[jss::result][jss::account_data][jss::Flags].asUInt();
4629 testcase(
"Rippling");
4630 using namespace jtx;
4646 auto const TSTA = A[
"TST"];
4647 auto const TSTB = B[
"TST"];
4656 env.
trust(TSTA(10'000), C);
4657 env.
trust(TSTB(10'000), C);
4658 env(pay(A, C, TSTA(10'000)));
4659 env(pay(B, C, TSTB(10'000)));
4660 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
4661 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
4668 env(pay(C, D,
STAmount{ammIss, 10}),
4670 path(amm.ammAccount()),
4679 testcase(
"AMMAndCLOB, offer quality change");
4680 using namespace jtx;
4681 auto const gw =
Account(
"gw");
4682 auto const TST = gw[
"TST"];
4683 auto const LP1 =
Account(
"LP1");
4684 auto const LP2 =
Account(
"LP2");
4686 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4687 Env env(*
this, features);
4688 env.
fund(
XRP(30'000'000'000), gw);
4689 env(offer(gw,
XRP(11'500'000'000), TST(1'000'000'000)));
4693 env(offer(LP1, TST(25),
XRPAmount(287'500'000)));
4698 env(offer(LP2, TST(25),
XRPAmount(287'500'000)));
4710 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
4716 lp2TakerGets = offer[
"taker_gets"].asString();
4717 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
4722 if (!features[fixAMMv1_1])
4726 STAmount{TST, UINT64_C(1'68737984885388), -14}),
4732 STAmount{TST, UINT64_C(1'68737976189735), -14}),
4741 BEAST_EXPECT(lp2TakerGets == offer[
"taker_gets"].asString());
4743 lp2TakerPays == offer[
"taker_pays"][
"value"].asString());
4750 testcase(
"Trading Fee");
4751 using namespace jtx;
4755 [&](
AMM& ammAlice,
Env& env) {
4757 ammAlice.
deposit(carol, USD(3'000));
4761 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4763 ammAlice.
vote(alice, 1'000);
4767 ammAlice.
deposit(carol, USD(3'000));
4769 carol,
IOUAmount{994'981155689671, -12}));
4770 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4772 ammAlice.
vote(alice, 0);
4778 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
4780 {{USD(1'000), EUR(1'000)}},
4788 [&](
AMM& ammAlice,
Env& env) {
4790 auto tokensFee = ammAlice.
deposit(
4791 carol, USD(1'000), std::nullopt,
STAmount{USD, 1, -1});
4794 ammAlice.
vote(alice, 0);
4796 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4799 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4800 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4810 [&](
AMM& ammAlice,
Env& env) {
4812 auto const tokensFee = ammAlice.
deposit(
4813 carol, USD(200), std::nullopt,
STAmount{USD, 2020, -6});
4816 ammAlice.
vote(alice, 0);
4818 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4821 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4822 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4831 [&](
AMM& ammAlice,
Env& env) {
4833 ammAlice.
deposit(carol, USD(3'000));
4836 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4838 ammAlice.
vote(alice, 1'000);
4845 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
4847 {{USD(1'000), EUR(1'000)}},
4854 [&](
AMM& ammAlice,
Env& env) {
4855 ammAlice.
deposit(carol, 1'000'000);
4856 auto const tokensFee = ammAlice.
withdraw(
4857 carol, USD(100), std::nullopt,
IOUAmount{520, 0});
4859 auto const balanceAfterWithdraw = [&]() {
4860 if (!features[fixAMMv1_1])
4861 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
4862 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
4864 BEAST_EXPECT(env.
balance(carol, USD) == balanceAfterWithdraw);
4866 auto const deposit = balanceAfterWithdraw - USD(29'000);
4867 ammAlice.
deposit(carol, deposit);
4869 ammAlice.
vote(alice, 0);
4871 auto const tokensNoFee = ammAlice.
withdraw(carol, deposit);
4872 if (!features[fixAMMv1_1])
4875 STAmount(USD, UINT64_C(30'443'43891402717), -11));
4879 STAmount(USD, UINT64_C(30'443'43891402716), -11));
4882 if (!features[fixAMMv1_1])
4884 tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4887 tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4888 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4897 [&](
AMM& ammAlice,
Env& env) {
4903 {USD(1'000), EUR(1'000)},
4906 BEAST_EXPECT(
expectLine(env, alice, EUR(28'990)));
4907 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4908 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4910 env(pay(carol, alice, EUR(10)),
4916 BEAST_EXPECT(
expectLine(env, alice, EUR(29'000)));
4917 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4918 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4921 ammAlice.
vote(alice, 1'000);
4924 env(pay(bob, carol, USD(10)),
4931 env, bob,
STAmount{EUR, UINT64_C(989'8989898989899), -13}));
4933 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4936 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
4939 {{USD(1'000), EUR(1'010)}},
4946 [&](
AMM& ammAlice,
Env& env) {
4948 env(offer(carol, EUR(10), USD(10)));
4950 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4951 BEAST_EXPECT(
expectLine(env, carol, EUR(30'010)));
4953 env(offer(carol, USD(10), EUR(10)));
4956 ammAlice.
vote(alice, 500);
4958 env(offer(carol, EUR(10), USD(10)));
4965 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
4969 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
4975 STAmount{EUR, UINT64_C(5'025125628140703), -15},
4976 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
4977 if (!features[fixAMMv1_1])
4980 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4981 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4987 STAmount{USD, UINT64_C(1'004'97487437186), -11},
4988 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4992 {{USD(1'000), EUR(1'010)}},
5002 Env env(*
this, features);
5007 {alice, bob, carol, ed},
5009 {USD(2'000), EUR(2'000)});
5010 env(offer(carol, EUR(5), USD(5)));
5011 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
5012 env(pay(bob, ed, USD(10)),
5016 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5017 if (!features[fixAMMv1_1])
5019 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5021 USD(1'000), EUR(1'005), ammAlice.
tokens()));
5026 env, bob,
STAmount(EUR, UINT64_C(1989'999999999999), -12)));
5029 STAmount(EUR, UINT64_C(1005'000000000001), -12),
5038 Env env(*
this, features);
5043 {alice, bob, carol, ed},
5045 {USD(2'000), EUR(2'000)});
5046 env(offer(carol, EUR(5), USD(5)));
5048 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 250);
5049 env(pay(bob, ed, USD(10)),
5053 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5054 if (!features[fixAMMv1_1])
5059 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
5062 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
5070 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
5073 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
5083 Env env(*
this, features);
5088 {alice, bob, carol, ed},
5090 {USD(2'000), EUR(2'000)});
5091 env(offer(carol, EUR(10), USD(10)));
5093 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5094 env(pay(bob, ed, USD(10)),
5098 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5099 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5101 USD(1'005), EUR(1'000), ammAlice.
tokens()));
5110 Env env(*
this, features);
5115 {alice, bob, carol, ed},
5117 {USD(2'000), EUR(2'000)});
5118 env(offer(carol, EUR(9), USD(9)));
5120 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5121 env(pay(bob, ed, USD(10)),
5125 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5127 env, bob,
STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
5130 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
5139 testcase(
"Adjusted Deposit/Withdraw Tokens");
5141 using namespace jtx;
5145 [&](
AMM& ammAlice,
Env& env) {
5153 Account const nataly(
"nataly");
5157 {bob, ed, paul, dan, chris, simon, ben, nataly},
5160 for (
int i = 0; i < 10; ++i)
5164 ammAlice.
deposit(simon, USD(0.1));
5166 ammAlice.
deposit(chris, USD(1));
5168 ammAlice.
deposit(dan, USD(10));
5170 ammAlice.
deposit(bob, USD(100));
5172 ammAlice.
deposit(carol, USD(1'000));
5174 ammAlice.
deposit(ed, USD(10'000));
5176 ammAlice.
deposit(paul, USD(100'000));
5178 ammAlice.
deposit(nataly, USD(1'000'000));
5184 if (!features[fixAMMv1_1])
5187 STAmount{USD, UINT64_C(10'000'0000000013), -10},
5192 BEAST_EXPECT(
expectLine(env, ben, USD(1'500'000)));
5193 BEAST_EXPECT(
expectLine(env, simon, USD(1'500'000)));
5194 BEAST_EXPECT(
expectLine(env, chris, USD(1'500'000)));
5195 BEAST_EXPECT(
expectLine(env, dan, USD(1'500'000)));
5196 if (!features[fixAMMv1_1])
5200 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
5202 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5203 BEAST_EXPECT(
expectLine(env, ed, USD(1'500'000)));
5204 BEAST_EXPECT(
expectLine(env, paul, USD(1'500'000)));
5205 if (!features[fixAMMv1_1])
5209 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
5214 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
5217 if (!features[fixAMMv1_1])
5221 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
5223 BEAST_EXPECT(
expectLine(env, alice, USD(30'000)));
5234 testAMM([&](
AMM& ammAlice,
Env& env) {
5242 Account const nataly(
"nataly");
5246 {bob, ed, paul, dan, chris, simon, ben, nataly},
5250 for (
int i = 0; i < 10; ++i)
5277 auto const xrpBalance = (
XRP(2'000'000) -
txfee(env, 20)).getText();
5295 testcase(
"Auto Delete");
5297 using namespace jtx;
5308 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5309 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5314 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5319 amm.withdrawAll(gw);
5320 BEAST_EXPECT(amm.ammExists());
5345 env(trust(alice,
STAmount{amm.lptIssue(), 10'000}),
5360 amm.expectBalances(
XRP(10'000), USD(10'000), amm.tokens()));
5361 BEAST_EXPECT(amm.expectTradingFee(1'000));
5362 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5366 amm.withdrawAll(alice);
5367 BEAST_EXPECT(!amm.ammExists());
5368 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5379 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5380 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5385 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5389 amm.withdrawAll(gw);
5390 BEAST_EXPECT(amm.ammExists());
5394 BEAST_EXPECT(amm.ammExists());
5396 amm.ammDelete(alice);
5397 BEAST_EXPECT(!amm.ammExists());
5398 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5408 testcase(
"Clawback");
5409 using namespace jtx;
5413 AMM amm(env, gw,
XRP(1'000), USD(1'000));
5421 using namespace jtx;
5422 testAMM([&](
AMM& amm,
Env& env) {
5423 amm.setClose(
false);
5424 auto const info = env.
rpc(
5428 "{\"account\": \"" + to_string(amm.ammAccount()) +
"\"}"));
5432 info[jss::result][jss::account_data][jss::AMMID]
5433 .asString() == to_string(amm.ammID()));
5439 amm.deposit(carol, 1'000);
5440 auto affected = env.
meta()->getJson(
5441 JsonOptions::none)[sfAffectedNodes.fieldName];
5445 for (
auto const& node : affected)
5447 if (node.isMember(sfModifiedNode.fieldName) &&
5448 node[sfModifiedNode.fieldName]
5449 [sfLedgerEntryType.fieldName]
5450 .asString() ==
"AccountRoot" &&
5451 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
5453 .asString() == to_string(amm.ammAccount()))
5455 found = node[sfModifiedNode.fieldName]
5456 [sfFinalFields.fieldName][jss::AMMID]
5457 .asString() == to_string(amm.ammID());
5461 BEAST_EXPECT(found);
5473 testcase(
"Offer/Strand Selection");
5474 using namespace jtx;
5477 auto const ETH = gw1[
"ETH"];
5478 auto const CAN = gw1[
"CAN"];
5484 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5485 fund(env, gw, {alice, carol, bob, ed},
XRP(2'000), {USD(2'000)});
5490 {alice, carol, bob, ed},
5491 {ETH(2'000), CAN(2'000)},
5493 env(
rate(gw, gwRate));
5494 env(
rate(gw1, gw1Rate));
5498 for (
auto const& rates :
5513 for (
auto i = 0; i < 3; ++i)
5515 Env env(*
this, features);
5516 prep(env, rates.first, rates.second);
5518 if (i == 0 || i == 2)
5524 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5525 env(pay(carol, bob, USD(100)),
5532 BEAST_EXPECT(amm->expectBalances(
5533 USD(1'000), ETH(1'000), amm->tokens()));
5535 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5536 q[i] = Quality(Amounts{
5537 ETH(2'000) - env.
balance(carol, ETH),
5538 env.
balance(bob, USD) - USD(2'000)});
5541 BEAST_EXPECT(q[0] > q[1]);
5543 BEAST_EXPECT(q[0] == q[2]);
5550 for (
auto i = 0; i < 3; ++i)
5552 Env env(*
this, features);
5553 prep(env, rates.first, rates.second);
5555 if (i == 0 || i == 2)
5561 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5562 env(offer(alice, USD(400), ETH(400)));
5567 BEAST_EXPECT(amm->expectBalances(
5568 USD(1'000), ETH(1'000), amm->tokens()));
5570 if (i == 0 || i == 2)
5579 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
5590 for (
auto i = 0; i < 3; ++i)
5592 Env env(*
this, features);
5593 prep(env, rates.first, rates.second);
5595 if (i == 0 || i == 2)
5601 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5602 env(pay(carol, bob, USD(100)),
5609 BEAST_EXPECT(!amm->expectBalances(
5610 USD(1'000), ETH(1'000), amm->tokens()));
5612 if (i == 2 && !features[fixAMMv1_1])
5614 if (rates.first == 1.5)
5616 if (!features[fixAMMv1_1])
5624 UINT64_C(378'6327949540823),
5628 UINT64_C(283'9745962155617),
5638 UINT64_C(378'6327949540813),
5642 UINT64_C(283'974596215561),
5647 if (!features[fixAMMv1_1])
5655 UINT64_C(325'299461620749),
5659 UINT64_C(243'9745962155617),
5669 UINT64_C(325'299461620748),
5673 UINT64_C(243'974596215561),
5679 if (rates.first == 1.5)
5687 ETH, UINT64_C(378'6327949540812), -13},
5690 UINT64_C(283'9745962155609),
5701 ETH, UINT64_C(325'2994616207479), -13},
5704 UINT64_C(243'9745962155609),
5708 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5709 q[i] = Quality(Amounts{
5710 ETH(2'000) - env.
balance(carol, ETH),
5711 env.
balance(bob, USD) - USD(2'000)});
5714 BEAST_EXPECT(q[1] > q[0]);
5716 BEAST_EXPECT(q[2] > q[1]);
5720 for (
auto i = 0; i < 3; ++i)
5722 Env env(*
this, features);
5723 prep(env, rates.first, rates.second);
5725 if (i == 0 || i == 2)
5731 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5732 env(offer(alice, USD(250), ETH(400)));
5737 BEAST_EXPECT(!amm->expectBalances(
5738 USD(1'000), ETH(1'000), amm->tokens()));
5744 if (rates.first == 1.5)
5746 if (!features[fixAMMv1_1])
5749 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
5756 USD, UINT64_C(40'5694150420947), -13},
5758 ETH, UINT64_C(64'91106406735152), -14},
5772 ETH, UINT64_C(335'0889359326475), -13},
5774 USD, UINT64_C(209'4305849579047), -13},
5781 if (!features[fixAMMv1_1])
5790 ETH, UINT64_C(335'0889359326485), -13},
5792 USD, UINT64_C(209'4305849579053), -13},
5805 ETH, UINT64_C(335'0889359326475), -13},
5807 USD, UINT64_C(209'4305849579047), -13},
5833 for (
auto i = 0; i < 3; ++i)
5835 Env env(*
this, features);
5836 prep(env, rates.first, rates.second);
5839 if (i == 0 || i == 2)
5847 amm.emplace(env, ed, ETH(1'000), USD(1'000));
5849 env(pay(carol, bob, USD(100)),
5855 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5857 if (i == 2 && !features[fixAMMv1_1])
5859 if (rates.first == 1.5)
5862 BEAST_EXPECT(amm->expectBalances(
5863 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
5869 BEAST_EXPECT(amm->expectBalances(
5871 ETH, UINT64_C(1'179'540094339627), -12},
5872 STAmount{USD, UINT64_C(847'7880529867501), -13},
5881 UINT64_C(343'3179205198749),
5885 UINT64_C(343'3179205198749),
5891 UINT64_C(362'2119470132499),
5895 UINT64_C(362'2119470132499),
5902 if (rates.first == 1.5)
5905 BEAST_EXPECT(amm->expectBalances(
5907 ETH, UINT64_C(1'176'660389557593), -12},
5913 BEAST_EXPECT(amm->expectBalances(
5914 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
5915 STAmount{USD, UINT64_C(847'7880529867501), -13},
5924 UINT64_C(343'3179205198749),
5928 UINT64_C(343'3179205198749),
5934 UINT64_C(362'2119470132499),
5938 UINT64_C(362'2119470132499),
5943 q[i] = Quality(Amounts{
5944 ETH(2'000) - env.
balance(carol, ETH),
5945 env.
balance(bob, USD) - USD(2'000)});
5947 BEAST_EXPECT(q[1] > q[0]);
5948 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5956 testcase(
"Fix Default Inner Object");
5957 using namespace jtx;
5968 Env env(*
this, features);
5969 fund(env, gw, {alice},
XRP(1'000), {USD(10)});
5975 {.tfee = tfee, .close = closeLedger});
5976 amm.deposit(alice, USD(10),
XRP(10));
5979 .
account = gw, .asset1Out = USD(1), .err =
ter(err2)});
5987 .
account = gw, .asset1Out = USD(2), .err =
ter(err4)});
5994 all - fixInnerObjTemplate,
6009 all - fixInnerObjTemplate,
6021 all - fixInnerObjTemplate,
6030 all - fixInnerObjTemplate,
6044 all - fixInnerObjTemplate,
6056 testcase(
"Fix changeSpotPriceQuality");
6057 using namespace jtx;
6060 SucceedShouldSucceedResize,
6072 auto const xrpIouAmounts10_100 =
6074 auto const iouXrpAmounts10_100 =
6079 {
"0.001519763260828713",
"1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
6080 {
"0.01099814367603737",
"1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
6081 {
"0.78",
"796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
6082 {
"105439.2955578965",
"49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
6083 {
"12408293.23445213",
"4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
6084 {
"1892611",
"0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
6085 {
"423028.8508101858",
"3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
6086 {
"44565388.41001027",
"73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
6087 {
"66831.68494832662",
"16", Quality{6346111134641742975}, 0, FailShouldSucceed},
6088 {
"675.9287302203422",
"1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
6089 {
"7047.112186735699",
"1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
6090 {
"840236.4402981238",
"47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
6091 {
"992715.618909774",
"189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
6092 {
"504636667521",
"185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
6093 {
"992706.7218636649",
"189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
6094 {
"1.068737911388205",
"127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
6095 {
"17932506.56880419",
"189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
6096 {
"1.066379294658174",
"128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
6097 {
"350131413924",
"1576879.110907892", Quality{6487411636539049449}, 650,
Fail},
6098 {
"422093460",
"2.731797662057464", Quality{6702911108534394924}, 1000,
Fail},
6099 {
"76128132223",
"367172.7148422662", Quality{6487263463413514240}, 548,
Fail},
6100 {
"132701839250",
"280703770.7695443", Quality{6273750681188885075}, 562,
Fail},
6101 {
"994165.7604612011",
"189551302411", Quality{5697835592690668727}, 815,
Fail},
6102 {
"45053.33303227917",
"86612695359", Quality{5625695218943638190}, 500,
Fail},
6103 {
"199649.077043865",
"14017933007", Quality{5766034667318524880}, 324,
Fail},
6104 {
"27751824831.70903",
"78896950", Quality{6272538159621630432}, 500,
Fail},
6105 {
"225.3731275781907",
"156431793648", Quality{5477818047604078924}, 989,
Fail},
6106 {
"199649.077043865",
"14017933007", Quality{5766036094462806309}, 324,
Fail},
6107 {
"3.590272027140361",
"20677643641", Quality{5406056147042156356}, 808,
Fail},
6108 {
"1.070884664490231",
"127604712776", Quality{5268620608623825741}, 293,
Fail},
6109 {
"3272.448829820197",
"6275124076", Quality{5625710328924117902}, 81,
Fail},
6110 {
"0.009059512633902926",
"7994028", Quality{5477511954775533172}, 1000,
Fail},
6111 {
"1",
"1.0", Quality{0}, 100,
Fail},
6112 {
"1.0",
"1", Quality{0}, 100,
Fail},
6113 {
"10",
"10.0", Quality{xrpIouAmounts10_100}, 100,
Fail},
6114 {
"10.0",
"10", Quality{iouXrpAmounts10_100}, 100,
Fail},
6115 {
"69864389131",
"287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
6116 {
"4328342973",
"12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
6117 {
"32347017",
"7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
6118 {
"61697206161",
"36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
6119 {
"1654524979",
"7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
6120 {
"88621.22277293179",
"5128418948", Quality{5766347291552869205}, 380, Succeed},
6121 {
"1892611",
"0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
6122 {
"4542.639373338766",
"24554809", Quality{5838994982188783710}, 0, Succeed},
6123 {
"5132932546",
"88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
6124 {
"78929964.1549083",
"1506494795", Quality{5986890029845558688}, 589, Succeed},
6125 {
"10096561906",
"44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
6126 {
"5092.219565514988",
"8768257694", Quality{5626349534958379008}, 503, Succeed},
6127 {
"1819778294",
"8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
6128 {
"6970462.633911943",
"57359281", Quality{6054087899185946624}, 850, Succeed},
6129 {
"3983448845",
"2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
6133 {
"771493171",
"1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
6137 boost::regex rx(
"^\\d+$");
6138 boost::smatch match;
6141 Env env(*
this, features);
6142 auto rules = env.
current()->rules();
6144 for (
auto const& t : tests)
6151 auto const& quality = std::get<Quality>(t);
6152 auto const tfee = std::get<std::uint16_t>(t);
6153 auto const status = std::get<Status>(t);
6154 auto const poolInIsXRP =
6155 boost::regex_search(std::get<0>(t), match, rx);
6156 auto const poolOutIsXRP =
6157 boost::regex_search(std::get<1>(t), match, rx);
6158 assert(!(poolInIsXRP && poolOutIsXRP));
6159 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
6160 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
6164 Amounts{poolIn, poolOut},
6171 if (status == SucceedShouldSucceedResize)
6173 if (!features[fixAMMv1_1])
6174 BEAST_EXPECT(Quality{*amounts} < quality);
6176 BEAST_EXPECT(Quality{*amounts} >= quality);
6178 else if (status == Succeed)
6180 if (!features[fixAMMv1_1])
6182 Quality{*amounts} >= quality ||
6184 Quality{*amounts}, quality,
Number{1, -7}));
6186 BEAST_EXPECT(Quality{*amounts} >= quality);
6188 else if (status == FailShouldSucceed)
6191 features[fixAMMv1_1] &&
6192 Quality{*amounts} >= quality);
6194 else if (status == SucceedShouldFail)
6197 !features[fixAMMv1_1] &&
6198 Quality{*amounts} < quality &&
6200 Quality{*amounts}, quality,
Number{1, -7}));
6209 if (status ==
Fail && quality != Quality{0})
6211 auto tinyOffer = [&]() {
6218 Amounts{poolIn, poolOut},
6222 else if (
isXRP(poolOut))
6227 Amounts{poolIn, poolOut},
6232 auto const takerPays = toAmount<STAmount>(
6237 Amounts{poolIn, poolOut}, takerPays, tfee)};
6239 BEAST_EXPECT(Quality(tinyOffer) < quality);
6241 else if (status == FailShouldSucceed)
6243 BEAST_EXPECT(!features[fixAMMv1_1]);
6245 else if (status == SucceedShouldFail)
6247 BEAST_EXPECT(features[fixAMMv1_1]);
6254 !strcmp(e.
what(),
"changeSpotPriceQuality failed"));
6256 !features[fixAMMv1_1] && status == FailShouldSucceed);
6265 BEAST_EXPECT(!res.has_value());
6272 using namespace jtx;
6274 testAMM([&](
AMM& ammAlice,
Env& env) {
6282 testAMM([&](
AMM& ammAlice,
Env& env) {
6290 testAMM([&](
AMM& ammAlice,
Env& env) {
6298 testAMM([&](
AMM& ammAlice,
Env& env) {
6301 .asset2Out =
XRP(100),
6307 testAMM([&](
AMM& ammAlice,
Env& env) {
6310 .asset2Out = BAD(100),
6316 testAMM([&](
AMM& ammAlice,
Env& env) {
6318 jv[jss::TransactionType] = jss::AMMWithdraw;
6320 jv[jss::Account] = alice.human();
6322 XRP(100).value().setJson(jv[jss::Amount]);
6323 USD(100).value().setJson(jv[jss::EPrice]);
6331 using namespace jtx;
6335 Account const gatehub{
"gatehub"};
6336 Account const bitstamp{
"bitstamp"};
6337 Account const trader{
"trader"};
6338 auto const usdGH = gatehub[
"USD"];
6339 auto const btcGH = gatehub[
"BTC"];
6340 auto const usdBIT = bitstamp[
"USD"];
6344 char const* testCase;
6345 double const poolUsdBIT;
6346 double const poolUsdGH;
6358 double const offer1BtcGH = 0.1;
6359 double const offer2BtcGH = 0.1;
6360 double const offer2UsdGH = 1;
6361 double const rateBIT = 0.0;
6362 double const rateGH = 0.0;
6367 for (
auto const& input : {
6369 .testCase =
"Test Fix Overflow Offer",
6372 .sendMaxUsdBIT{usdBIT(50)},
6373 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6376 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6377 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6378 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6379 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6380 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6381 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6382 .lpTokenBalance = {28'61817604250837, -14},
6390 .testCase =
"Overflow test {1, 100, 0.111}",
6393 .sendMaxUsdBIT{usdBIT(0.111)},
6394 .sendUsdGH{usdGH, 100},
6397 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6398 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6399 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6400 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6401 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6402 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6403 .lpTokenBalance{10, 0},
6404 .offer1BtcGH = 1e-5,
6406 .offer2UsdGH = 1e-5,
6411 .testCase =
"Overflow test {1, 100, 1.00}",
6414 .sendMaxUsdBIT{usdBIT(1.00)},
6415 .sendUsdGH{usdGH, 100},
6418 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6419 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6420 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6421 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6422 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6423 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6424 .lpTokenBalance{10, 0},
6425 .offer1BtcGH = 1e-5,
6427 .offer2UsdGH = 1e-5,
6432 .testCase =
"Overflow test {1, 100, 4.6432}",
6435 .sendMaxUsdBIT{usdBIT(4.6432)},
6436 .sendUsdGH{usdGH, 100},
6439 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6440 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6441 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6442 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6443 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6444 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6445 .lpTokenBalance{10, 0},
6446 .offer1BtcGH = 1e-5,
6448 .offer2UsdGH = 1e-5,
6453 .testCase =
"Overflow test {1, 100, 10}",
6456 .sendMaxUsdBIT{usdBIT(10)},
6457 .sendUsdGH{usdGH, 100},
6460 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6461 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6462 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6463 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6464 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6465 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6466 .lpTokenBalance{10, 0},
6467 .offer1BtcGH = 1e-5,
6469 .offer2UsdGH = 1e-5,
6474 .testCase =
"Overflow test {50, 100, 5.55}",
6477 .sendMaxUsdBIT{usdBIT(5.55)},
6478 .sendUsdGH{usdGH, 100},
6481 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6482 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6483 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6484 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6485 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6486 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6487 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6488 .offer1BtcGH = 1e-5,
6490 .offer2UsdGH = 1e-5,
6495 .testCase =
"Overflow test {50, 100, 50.00}",
6498 .sendMaxUsdBIT{usdBIT(50.00)},
6499 .sendUsdGH{usdGH, 100},
6500 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6501 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6502 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6503 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6504 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6505 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6506 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6507 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6508 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6509 .offer1BtcGH = 1e-5,
6511 .offer2UsdGH = 1e-5,
6516 .testCase =
"Overflow test {50, 100, 232.16}",
6519 .sendMaxUsdBIT{usdBIT(232.16)},
6520 .sendUsdGH{usdGH, 100},
6523 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6524 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6525 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6526 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6527 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6528 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6529 .lpTokenBalance{70'71067811865475, -14},
6530 .offer1BtcGH = 1e-5,
6532 .offer2UsdGH = 1e-5,
6537 .testCase =
"Overflow test {50, 100, 500}",
6540 .sendMaxUsdBIT{usdBIT(500)},
6541 .sendUsdGH{usdGH, 100},
6544 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6545 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6546 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6547 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6548 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6549 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6550 .lpTokenBalance{70'71067811865475, -14},
6551 .offer1BtcGH = 1e-5,
6553 .offer2UsdGH = 1e-5,
6559 testcase(input.testCase);
6560 for (
auto const& features :
6561 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
6563 Env env(*
this, features);
6565 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6568 if (input.rateGH != 0.0)
6569 env(
rate(gatehub, input.rateGH));
6570 if (input.rateBIT != 0.0)
6571 env(
rate(bitstamp, input.rateBIT));
6573 env(trust(trader, usdGH(10'000'000)));
6574 env(trust(trader, usdBIT(10'000'000)));
6575 env(trust(trader, btcGH(10'000'000)));
6578 env(pay(gatehub, trader, usdGH(100'000)));
6579 env(pay(gatehub, trader, btcGH(100'000)));
6580 env(pay(bitstamp, trader, usdBIT(100'000)));
6586 usdGH(input.poolUsdGH),
6587 usdBIT(input.poolUsdBIT)};
6591 amm.getLPTokensBalance();
6593 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6596 btcGH(input.offer2BtcGH),
6597 usdGH(input.offer2UsdGH)));
6600 env(pay(trader, trader, input.sendUsdGH),
6602 path(~btcGH, ~usdGH),
6607 auto const failUsdGH =
6608 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6609 auto const failUsdBIT =
6610 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6611 auto const goodUsdGH =
6612 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6613 auto const goodUsdBIT =
6614 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6615 if (!features[fixAMMOverflowOffer])
6617 BEAST_EXPECT(amm.expectBalances(
6618 failUsdGH, failUsdBIT, input.lpTokenBalance));
6622 BEAST_EXPECT(amm.expectBalances(
6623 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
6628 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6632 Number const sqrtPoolProduct =
6633 root2(goodUsdGH * goodUsdBIT);
6644 (sqrtPoolProduct +
Number{1, -14} >=
6645 input.lpTokenBalance));
6654 testcase(
"swapRounding");
6655 using namespace jtx;
6657 const STAmount xrpPool{
XRP, UINT64_C(51600'000981)};
6658 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
6660 const STAmount xrpBob{
XRP, UINT64_C(1092'878933)};
6662 USD, UINT64_C(3'988035892323031), -28};
6665 [&](
AMM& amm,
Env& env) {
6667 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(
XRP, USD);
6670 env.
fund(xrpBob, bob);
6671 env.
trust(USD(1'000'000), bob);
6672 env(pay(gw, bob, iouBob));
6675 env(offer(bob,
XRP(6300), USD(100'000)));
6680 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
6682 {{xrpPool, iouPool}},
6685 {jtx::supported_amendments() | fixAMMv1_1});
6691 testcase(
"AMM Offer Blocked By LOB");
6692 using namespace jtx;
6698 Env env(*
this, features);
6700 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6702 env(offer(alice,
XRP(1), USD(0.01)));
6705 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6709 env(offer(carol, USD(0.49),
XRP(1)));
6712 if (!features[fixAMMv1_1])
6714 BEAST_EXPECT(amm.expectBalances(
6715 XRP(200'000), USD(100'000), amm.tokens()));
6717 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6720 env, carol, 1, {{Amounts{USD(0.49),
XRP(1)}}}));
6724 BEAST_EXPECT(amm.expectBalances(
6725 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6727 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6736 Env env(*
this, features);
6738 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6742 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6745 env(offer(carol, USD(0.49),
XRP(1)));
6749 BEAST_EXPECT(amm.expectBalances(
6750 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6757 Env env(*
this, features);
6758 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6762 env(offer(bob, USD(1),
XRPAmount(500)));
6764 AMM amm(env, alice,
XRP(1'000), USD(500));
6765 env(offer(carol,
XRP(100), USD(55)));
6767 if (!features[fixAMMv1_1])
6770 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
6772 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6774 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
6778 BEAST_EXPECT(amm.expectBalances(
6780 STAmount{USD, UINT64_C(550'000000055), -9},
6788 STAmount{USD, 4'99999995, -8}}}}));
6790 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6797 Env env(*
this, features);
6798 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6800 AMM amm(env, alice,
XRP(1'000), USD(500));
6801 env(offer(carol,
XRP(100), USD(55)));
6803 BEAST_EXPECT(amm.expectBalances(
6805 STAmount{USD, UINT64_C(550'000000055), -9},
6819 using namespace jtx;
6823 Env env(*
this, features);
6829 {USD(1'000'000'000)});
6830 AMM amm(env, gw,
XRP(2), USD(1));
6831 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6832 amm.deposit(carol,
IOUAmount{1'000'000});
6833 amm.withdrawAll(alice);
6834 amm.withdrawAll(carol);
6836 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
6837 auto const lpTokenBalance =
6838 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6840 lpToken ==
"1414.213562373095" &&
6841 lpTokenBalance ==
"1414.213562373");
6842 if (!features[fixAMMv1_1])
6845 BEAST_EXPECT(amm.ammExists());
6849 amm.withdrawAll(gw);
6850 BEAST_EXPECT(!amm.ammExists());
6856 for (
auto const& lp : {gw, bob})
6858 Env env(*
this, features);
6859 auto const ABC = gw[
"ABC"];
6863 {alice, carol, bob},
6865 {USD(1'000'000'000), ABC(1'000'000'000'000)});
6866 AMM amm(env, lp, ABC(2'000'000), USD(1));
6867 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6868 amm.deposit(carol,
IOUAmount{1'000'000});
6869 amm.withdrawAll(alice);
6870 amm.withdrawAll(carol);
6872 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
6873 auto const lpTokenBalance =
6874 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6876 lpToken ==
"1414.213562373095" &&
6877 lpTokenBalance ==
"1414.213562373");
6878 if (!features[fixAMMv1_1])
6881 BEAST_EXPECT(amm.ammExists());
6885 amm.withdrawAll(lp);
6886 BEAST_EXPECT(!amm.ammExists());
6893 Env env(*
this, features);
6894 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)});
6895 AMM amm(env, gw,
XRP(10), USD(10));
6896 amm.deposit(alice, 1'000);
6899 BEAST_EXPECT(res && !res.value());
6902 BEAST_EXPECT(res && !res.value());
6906 Env env(*
this, features);
6907 fund(env, gw, {alice},
XRP(1'000), {USD(1'000), EUR(1'000)});
6908 AMM amm(env, gw, EUR(10), USD(10));
6909 amm.deposit(alice, 1'000);
6912 BEAST_EXPECT(res && !res.value());
6915 BEAST_EXPECT(res && !res.value());
6919 Env env(*
this, features);
6921 auto const YAN = gw1[
"YAN"];
6922 fund(env, gw, {gw1},
XRP(1'000), {USD(1'000)});
6923 fund(env, gw1, {gw},
XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
6924 AMM amm(env, gw1, YAN(10), USD(10));
6925 amm.deposit(gw, 1'000);
6928 BEAST_EXPECT(res && !res.value());
6930 BEAST_EXPECT(res && !res.value());
6937 testcase(
"test clawback from AMM account");
6938 using namespace jtx;
6941 Env env(*
this, features);
6944 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
6949 if (!features[featureAMMClawback])
6975 Issue usd(USD.issue().currency, amm.ammAccount());
6984 testcase(
"test AMMDeposit with frozen assets");
6985 using namespace jtx;
6992 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7002 Env env(*
this, features);
7003 testAMMDeposit(env, [&](
AMM& amm) {
7017 Env env(*
this, features);
7018 testAMMDeposit(env, [&](
AMM& amm) {
7029 if (features[featureAMMClawback])
7034 Env env(*
this, features);
7035 testAMMDeposit(env, [&](
AMM& amm) {
7050 Env env(*
this, features);
7051 testAMMDeposit(env, [&](
AMM& amm) {
7066 testcase(
"Fix Reserve Check On Withdrawal");
7067 using namespace jtx;
7072 auto test = [&](
auto&& cb) {
7073 Env env(*
this, features);
7074 auto const starting_xrp =
7075 reserve(env, 2) + env.
current()->fees().base * 5;
7076 env.
fund(starting_xrp, gw);
7077 env.
fund(starting_xrp, alice);
7078 env.
trust(USD(2'000), alice);
7080 env(pay(gw, alice, USD(2'000)));
7082 AMM amm(env, gw, EUR(1'000), USD(1'000));
7083 amm.deposit(alice, USD(1));
7088 test([&](
AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
7091 test([&](
AMM& amm) {
7094 .asset1Out = EUR(0.1),
7095 .asset2Out = USD(0.1),
7099 .asset1Out = USD(0.1),
7100 .asset2Out = EUR(0.1),
7105 test([&](
AMM& amm) {
7107 .
account = alice, .asset1Out = EUR(0.1), .err = err});
7116 testInvalidInstance();
7117 testInstanceCreate();
7118 testInvalidDeposit(all);
7119 testInvalidDeposit(all - featureAMMClawback);
7121 testInvalidWithdraw();
7123 testInvalidFeeVote();
7127 testBid(all - fixAMMv1_1);
7128 testInvalidAMMPayment();
7129 testBasicPaymentEngine(all);
7130 testBasicPaymentEngine(all - fixAMMv1_1);
7131 testBasicPaymentEngine(all - fixReducedOffersV2);
7132 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
7137 testAMMAndCLOB(all);
7138 testAMMAndCLOB(all - fixAMMv1_1);
7139 testTradingFee(all);
7140 testTradingFee(all - fixAMMv1_1);
7141 testAdjustedTokens(all);
7142 testAdjustedTokens(all - fixAMMv1_1);
7147 testSelection(all - fixAMMv1_1);
7148 testFixDefaultInnerObj();
7150 testFixOverflowOffer(all);
7151 testFixOverflowOffer(all - fixAMMv1_1);
7153 testFixChangeSpotPriceQuality(all);
7154 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
7155 testFixAMMOfferBlockedByLOB(all);
7156 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
7157 testLPTokenBalance(all);
7158 testLPTokenBalance(all - fixAMMv1_1);
7159 testAMMClawback(all);
7160 testAMMClawback(all - featureAMMClawback);
7161 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
7162 testAMMDepositWithFrozenAssets(all);
7163 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
7164 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
7165 testFixReserveCheckOnWithdrawal(all);
7166 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.