20#include <test/jtx/AMM.h>
21#include <test/jtx/AMMTest.h>
22#include <test/jtx/amount.h>
23#include <test/jtx/sendmax.h>
24#include <xrpld/app/misc/AMMHelpers.h>
25#include <xrpld/app/misc/AMMUtils.h>
26#include <xrpld/app/paths/AMMContext.h>
27#include <xrpld/app/paths/AMMOffer.h>
28#include <xrpld/app/tx/detail/AMMBid.h>
29#include <xrpld/rpc/RPCHandler.h>
30#include <xrpld/rpc/detail/RPCHelpers.h>
31#include <xrpl/basics/Number.h>
32#include <xrpl/protocol/AMMCore.h>
33#include <xrpl/protocol/Feature.h>
34#include <xrpl/protocol/STParsedJSON.h>
35#include <xrpl/resource/Fees.h>
41#include <boost/regex.hpp>
72 {{
USD(20'000),
BTC(0.5)}});
123 BEAST_EXPECT(amm.expectTradingFee(1'000));
124 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
151 BEAST_EXPECT(!ammAlice.ammExists());
160 BEAST_EXPECT(!ammAlice.ammExists());
168 BEAST_EXPECT(!ammAlice.ammExists());
185 BEAST_EXPECT(!ammAlice.ammExists());
194 BEAST_EXPECT(!ammAlice.ammExists());
203 BEAST_EXPECT(!ammAlice.ammExists());
222 BEAST_EXPECT(!ammAlice.ammExists());
247 BEAST_EXPECT(!ammAlice.ammExists());
312 auto const starting_xrp =
314 env.
fund(starting_xrp,
gw);
329 auto const starting_xrp =
331 env.
fund(starting_xrp,
gw);
386 auto const token1 = ammAlice.
lptIssue();
387 auto const token2 = ammAlice1.lptIssue();
412 auto const USD1 = gw1[
"USD"];
609 for (
auto const& it : invalidOptions)
628 jv[jss::TransactionType] = jss::AMMDeposit;
650 jv[jss::TransactionType] = jss::AMMDeposit;
655 jv[jss::LPTokenOut] =
858 [&](
AMM& ammAlice,
Env& env) {
860 if (!features[featureAMMClawback])
904 [&](
AMM& ammAlice,
Env& env) {
907 if (!features[featureAMMClawback])
966 [&](
AMM& ammAlice,
Env& env) {
1006 {{
USD(20'000),
BTC(0.5)}});
1010 Env env(*
this, features);
1025 if (features[featureAMMClawback])
1114 auto const starting_xrp =
1149 auto const starting_xrp =
1311 using namespace jtx;
1328 for (
const Number deltaLPTokens :
1329 {
Number{UINT64_C(100000'0000000009), -10},
1330 Number{UINT64_C(100000'0000000001), -10}})
1336 deltaLPTokens.
mantissa(), deltaLPTokens.exponent()};
1349 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1350 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1354 const Number fr = deltaLPTokens / 1e7;
1358 const Number deltaXRP = fr * 1e10;
1359 const Number deltaUSD = fr * 1e4;
1369 XRP(10'000) + depositXRP,
1370 USD(10'000) + depositUSD,
1494 STAmount{USD, UINT64_C(10'000'000001), -6},
1610 using namespace jtx;
1613 [&](
AMM& ammAlice,
Env& env) {
1623 [&](
AMM& ammAlice,
Env& env) {
1648 .asset1Out =
USD(100),
1777 for (
auto const& it : invalidOptions)
1788 ter(std::get<5>(it)));
1939 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
2044 [&](
AMM& ammAlice,
Env&) {
2056 [&](
AMM& ammAlice,
Env&) {
2127 using namespace jtx;
2295 [&](
AMM& ammAlice,
Env& env) {
2299 if (!env.
current()->rules().enabled(fixAMMv1_1))
2315 ammAlice.withdrawAll(
carol);
2321 {
all,
all - fixAMMv1_1});
2325 [&](AMM& ammAlice, Env& env) {
2326 ammAlice.deposit(carol, 1'000'000);
2328 carol, USD(0), std::nullopt, IOUAmount{520, 0});
2329 if (!env.current()->rules().enabled(fixAMMv1_1))
2331 ammAlice.expectBalances(
2332 XRPAmount(11'000'000'000),
2333 STAmount{USD, UINT64_C(9'372'781065088757), -12},
2334 IOUAmount{10'153'846'15384616, -8}) &&
2335 ammAlice.expectLPTokens(
2336 carol, IOUAmount{153'846'15384616, -8}));
2339 ammAlice.expectBalances(
2340 XRPAmount(11'000'000'000),
2341 STAmount{USD, UINT64_C(9'372'781065088769), -12},
2342 IOUAmount{10'153'846'15384616, -8}) &&
2343 ammAlice.expectLPTokens(
2344 carol, IOUAmount{153'846'15384616, -8}));
2349 {all, all - fixAMMv1_1});
2354 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
2355 env(
rate(gw, 1.25));
2358 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
2359 BEAST_EXPECT(ammAlice.expectBalances(
2360 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2361 BEAST_EXPECT(
expectLine(env, alice, USD(0)));
2362 BEAST_EXPECT(
expectLine(env, alice, BTC(0)));
2363 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
2365 ammAlice.deposit(carol, 10);
2366 BEAST_EXPECT(ammAlice.expectBalances(
2367 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
2368 BEAST_EXPECT(
expectLine(env, carol, USD(0)));
2369 BEAST_EXPECT(
expectLine(env, carol, BTC(0)));
2371 ammAlice.withdraw(carol, 10);
2372 BEAST_EXPECT(ammAlice.expectBalances(
2373 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2374 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
2375 BEAST_EXPECT(
expectLine(env, carol, USD(2'000)));
2376 BEAST_EXPECT(
expectLine(env, carol, BTC(0.05)));
2380 testAMM([&](AMM& ammAlice, Env&) {
2382 ammAlice.withdraw(alice, IOUAmount{1, -3});
2383 BEAST_EXPECT(ammAlice.expectBalances(
2384 XRPAmount{9'999'999'999},
2385 STAmount{USD, UINT64_C(9'999'999999), -6},
2386 IOUAmount{9'999'999'999, -3}));
2388 testAMM([&](AMM& ammAlice, Env&) {
2390 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
2391 BEAST_EXPECT(ammAlice.expectBalances(
2392 XRPAmount{9'999'999'999},
2394 IOUAmount{9'999'999'9995, -4}));
2396 testAMM([&](AMM& ammAlice, Env&) {
2398 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
2399 BEAST_EXPECT(ammAlice.expectBalances(
2401 STAmount{USD, UINT64_C(9'999'9999999999), -10},
2402 IOUAmount{9'999'999'99999995, -8}));
2407 testAMM([&](AMM& ammAlice, Env&) {
2408 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
2409 BEAST_EXPECT(ammAlice.expectBalances(
2410 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
2413 testAMM([&](AMM& ammAlice, Env&) {
2414 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
2415 BEAST_EXPECT(ammAlice.expectBalances(
2416 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
2419 testAMM([&](AMM& ammAlice, Env&) {
2420 ammAlice.withdraw(alice, IOUAmount{9'999'900},
XRP(0));
2421 BEAST_EXPECT(ammAlice.expectBalances(
2422 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2425 testAMM([&](AMM& ammAlice, Env&) {
2427 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
2428 BEAST_EXPECT(ammAlice.expectBalances(
2429 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
2432 testAMM([&](AMM& ammAlice, Env&) {
2433 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
2434 BEAST_EXPECT(ammAlice.expectBalances(
2435 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2442 testcase(
"Invalid Fee Vote");
2443 using namespace jtx;
2445 testAMM([&](
AMM& ammAlice,
Env& env) {
2496 testAMM([&](
AMM& ammAlice,
Env& env) {
2511 testcase(
"Fee Vote");
2512 using namespace jtx;
2515 testAMM([&](
AMM& ammAlice,
Env& env) {
2517 ammAlice.
vote({}, 1'000);
2523 auto vote = [&](
AMM& ammAlice,
2526 int fundUSD = 100'000,
2530 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2532 ammAlice.
vote(a, 50 * (i + 1));
2538 testAMM([&](
AMM& ammAlice,
Env& env) {
2539 for (
int i = 0; i < 7; ++i)
2540 vote(ammAlice, env, i, 10'000);
2546 testAMM([&](
AMM& ammAlice,
Env& env) {
2547 for (
int i = 0; i < 7; ++i)
2548 vote(ammAlice, env, i);
2551 ammAlice.
vote(a, 450);
2557 testAMM([&](
AMM& ammAlice,
Env& env) {
2558 for (
int i = 0; i < 7; ++i)
2559 vote(ammAlice, env, i);
2561 vote(ammAlice, env, 7, 100'000, 20'000'000);
2567 testAMM([&](
AMM& ammAlice,
Env& env) {
2568 for (
int i = 7; i > 0; --i)
2569 vote(ammAlice, env, i);
2571 vote(ammAlice, env, 0, 100'000, 20'000'000);
2578 testAMM([&](
AMM& ammAlice,
Env& env) {
2580 for (
int i = 0; i < 7; ++i)
2581 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2583 for (
int i = 0; i < 7; ++i)
2585 ammAlice.
deposit(carol, 10'000'000);
2586 ammAlice.
vote(carol, 1'000);
2595 testAMM([&](
AMM& ammAlice,
Env& env) {
2597 for (
int i = 0; i < 7; ++i)
2598 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2600 for (
int i = 0; i < 7; ++i)
2601 ammAlice.
withdraw(accounts[i], 9'000'000);
2602 ammAlice.
deposit(carol, 1'000);
2604 ammAlice.
vote(carol, 1'000);
2605 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2607 BEAST_EXPECT(info[i][jss::account] != carol.human());
2616 testcase(
"Invalid Bid");
2617 using namespace jtx;
2623 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2624 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2627 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2634 .bidMin = 1'000'000,
2642 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2643 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2646 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2653 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2659 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2663 amm.expectBalances(
XRP(1'000), USD(1'000),
IOUAmount{1}));
2666 BEAST_EXPECT(
Number{amm.getLPTokensBalance(gw)} == 1);
2678 testAMM([&](
AMM& ammAlice,
Env& env) {
2683 .flags = tfWithdrawAll,
2685 ter(temINVALID_FLAG));
2687 ammAlice.
deposit(carol, 1'000'000);
2689 for (
auto bid : {0, -100})
2695 ter(temBAD_AMOUNT));
2700 ter(temBAD_AMOUNT));
2709 ter(tecAMM_INVALID_TOKENS));
2719 ter(terNO_ACCOUNT));
2722 Account
const dan(
"dan");
2723 env.
fund(XRP(1'000), dan);
2728 ter(tecAMM_INVALID_TOKENS));
2732 ter(tecAMM_INVALID_TOKENS));
2738 .authAccounts = {bob},
2740 ter(terNO_ACCOUNT));
2746 .assets = {{USD, GBP}},
2753 .bidMax = STAmount{USD, 100},
2755 ter(temBAD_AMM_TOKENS));
2758 .bidMin = STAmount{USD, 100},
2760 ter(temBAD_AMM_TOKENS));
2764 testAMM([&](AMM& ammAlice, Env& env) {
2765 ammAlice.withdrawAll(alice);
2774 testAMM([&](AMM& ammAlice, Env& env) {
2776 Account bill(
"bill");
2777 Account scott(
"scott");
2778 Account james(
"james");
2779 env.fund(
XRP(1'000), bob, ed, bill, scott, james);
2781 ammAlice.deposit(carol, 1'000'000);
2785 .authAccounts = {bob, ed, bill, scott, james},
2791 testAMM([&](AMM& ammAlice, Env& env) {
2792 fund(env, gw, {bob},
XRP(1'000), {USD(100)}, Fund::Acct);
2793 ammAlice.deposit(carol, 1'000'000);
2794 ammAlice.deposit(bob, 10);
2797 .bidMin = 1'000'001,
2799 ter(tecAMM_INVALID_TOKENS));
2802 .bidMax = 1'000'001,
2804 ter(tecAMM_INVALID_TOKENS));
2809 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2814 ter(tecAMM_INVALID_TOKENS));
2820 fund(env, gw, {alice, bob},
XRP(1'000), {USD(1'000)});
2822 auto const lpIssue =
amm.lptIssue();
2823 env.trust(STAmount{lpIssue, 100}, alice);
2824 env.trust(STAmount{lpIssue, 50}, bob);
2825 env(
pay(gw, alice, STAmount{lpIssue, 100}));
2826 env(
pay(gw, bob, STAmount{lpIssue, 50}));
2827 env(
amm.bid({.account = alice, .bidMin = 100}));
2834 ter(tecAMM_FAILED));
2842 using namespace jtx;
2849 [&](
AMM& ammAlice,
Env& env) {
2850 ammAlice.
deposit(carol, 1'000'000);
2851 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2864 [&](
AMM& ammAlice,
Env& env) {
2865 ammAlice.
deposit(carol, 1'000'000);
2868 {.account = carol, .bidMin = 110, .bidMax = 110}));
2874 {.account = alice, .bidMin = 180, .bidMax = 200}));
2877 XRP(11'000), USD(11'000),
IOUAmount{10'999'814'5, -1}));
2886 [&](
AMM& ammAlice,
Env& env) {
2887 ammAlice.
deposit(carol, 1'000'000);
2889 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2892 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2893 ammAlice.
deposit(bob, 1'000'000);
2895 env(ammAlice.
bid({.account = bob}));
2906 env(ammAlice.
bid({.account = carol, .bidMax = 600}));
2920 {.account = carol, .bidMin = 100, .bidMax = 600}));
2931 [&](
AMM& ammAlice,
Env& env) {
2932 ammAlice.
deposit(carol, 1'000'000);
2934 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2935 ammAlice.
deposit(bob, 1'000'000);
2940 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2944 env(ammAlice.
bid({.account = bob}));
2950 env(ammAlice.
bid({.account = carol}));
2956 env(ammAlice.
bid({.account = bob}));
2962 0, std::nullopt,
IOUAmount{127'33875, -5}));
2965 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2970 XRP(12'000), USD(12'000),
IOUAmount{11'999'678'91, -2}));
2982 [&](
AMM& ammAlice,
Env& env) {
2985 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
2986 ammAlice.
deposit(bob, 1'000'000);
2987 ammAlice.
deposit(ed, 1'000'000);
2988 ammAlice.
deposit(carol, 500'000);
2989 ammAlice.
deposit(dan, 500'000);
2994 .authAccounts = {bob, ed},
2996 auto const slotPrice =
IOUAmount{5'200};
2997 ammTokens -= slotPrice;
3000 XRP(13'000), USD(13'000), ammTokens));
3002 for (
int i = 0; i < 10; ++i)
3004 auto tokens = ammAlice.
deposit(carol, USD(100));
3005 ammAlice.
withdraw(carol, tokens, USD(0));
3006 tokens = ammAlice.
deposit(bob, USD(100));
3007 ammAlice.
withdraw(bob, tokens, USD(0));
3008 tokens = ammAlice.
deposit(ed, USD(100));
3009 ammAlice.
withdraw(ed, tokens, USD(0));
3012 if (!features[fixAMMv1_1])
3016 STAmount(USD, UINT64_C(29'499'00572620545), -11));
3019 STAmount(USD, UINT64_C(18'999'00572616195), -11));
3022 STAmount(USD, UINT64_C(18'999'00572611841), -11));
3026 STAmount(USD, UINT64_C(13'002'98282151419), -11),
3033 STAmount(USD, UINT64_C(29'499'00572620544), -11));
3036 STAmount(USD, UINT64_C(18'999'00572616194), -11));
3039 STAmount(USD, UINT64_C(18'999'0057261184), -10));
3043 STAmount(USD, UINT64_C(13'002'98282151422), -11),
3048 for (
int i = 0; i < 10; ++i)
3050 auto const tokens = ammAlice.
deposit(dan, USD(100));
3051 ammAlice.
withdraw(dan, tokens, USD(0));
3056 if (!features[fixAMMv1_1])
3060 STAmount(USD, UINT64_C(19'490'056722744), -9));
3064 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3067 ammAlice.
deposit(carol, USD(100));
3071 STAmount{USD, UINT64_C(13'112'92609877019), -11},
3073 env(pay(carol, bob, USD(100)),
3081 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3088 STAmount(USD, UINT64_C(19'490'05672274399), -11));
3092 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3095 ammAlice.
deposit(carol, USD(100));
3099 STAmount{USD, UINT64_C(13'112'92609877023), -11},
3101 env(pay(carol, bob, USD(100)),
3109 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3118 if (!features[fixAMMv1_1])
3122 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3129 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3136 if (!features[fixAMMv1_1])
3139 STAmount(USD, UINT64_C(29'399'00572620545), -11));
3143 STAmount(USD, UINT64_C(29'399'00572620544), -11));
3145 for (
int i = 0; i < 10; ++i)
3147 auto const tokens = ammAlice.
deposit(carol, USD(100));
3148 ammAlice.
withdraw(carol, tokens, USD(0));
3152 if (!features[fixAMMv1_1])
3156 STAmount(USD, UINT64_C(29'389'06197177128), -11));
3159 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3166 STAmount(USD, UINT64_C(29'389'06197177124), -11));
3169 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3177 if (!features[fixAMMv1_1])
3181 STAmount{USD, UINT64_C(13'023'98038490681), -11},
3188 STAmount{USD, UINT64_C(13'023'98038490689), -11},
3199 [&](AMM& ammAlice, Env& env) {
3202 Number{STAmount::cMinValue, STAmount::cMinOffset};
3204 {.account = alice, .bidMin = IOUAmount{tiny}}));
3207 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
3209 BEAST_EXPECT(ammAlice.expectBalances(
3210 XRP(10'000), USD(10'000), ammAlice.tokens()));
3215 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
3218 BEAST_EXPECT(ammAlice.expectAuctionSlot(
3219 0, 0, IOUAmount{tiny * Number{105, -2}}));
3222 BEAST_EXPECT(ammAlice.expectBalances(
3223 XRP(10'000), USD(10'000), ammAlice.tokens()));
3232 [&](AMM& ammAlice, Env& env) {
3235 .bidMin = IOUAmount{100},
3236 .authAccounts = {carol},
3238 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
3239 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
3240 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3243 fund(env, {bob, dan},
XRP(1'000));
3246 .bidMin = IOUAmount{100},
3247 .authAccounts = {bob, dan},
3249 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3258 Env env(*
this, features);
3259 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3260 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3261 auto const lpIssue =
amm.lptIssue();
3262 env.trust(STAmount{lpIssue, 500}, alice);
3263 env.trust(STAmount{lpIssue, 50}, bob);
3264 env(
pay(gw, alice, STAmount{lpIssue, 500}));
3265 env(
pay(gw, bob, STAmount{lpIssue, 50}));
3267 env(
amm.bid({.account = alice, .bidMin = 500}));
3268 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3269 BEAST_EXPECT(
expectLine(env, alice, STAmount{lpIssue, 0}));
3272 env(
pay(alice, bob, USD(10)), path(~USD), sendmax(
XRP(11)));
3273 BEAST_EXPECT(
amm.expectBalances(
3274 XRPAmount{1'010'010'011},
3276 IOUAmount{1'004'487'562112089, -9}));
3278 env(
pay(bob, alice,
XRP(10)), path(~XRP), sendmax(USD(11)));
3279 if (!features[fixAMMv1_1])
3281 BEAST_EXPECT(
amm.expectBalances(
3282 XRPAmount{1'000'010'011},
3283 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3284 IOUAmount{1'004'487'562112089, -9}));
3288 BEAST_EXPECT(
amm.expectBalances(
3289 XRPAmount{1'000'010'011},
3290 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3291 IOUAmount{1'004'487'562112089, -9}));
3297 Env env(*
this, features);
3298 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3299 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3303 auto jtx = env.jt(tx, seq(1), fee(10));
3304 env.app().config().features.erase(featureAMM);
3305 PreflightContext pfctx(
3308 env.current()->rules(),
3311 auto pf = AMMBid::preflight(pfctx);
3312 BEAST_EXPECT(pf == temDISABLED);
3313 env.app().config().features.insert(featureAMM);
3317 auto jtx = env.jt(tx, seq(1), fee(10));
3318 jtx.jv[
"TxnSignature"] =
"deadbeef";
3319 jtx.stx = env.ust(jtx);
3320 PreflightContext pfctx(
3323 env.current()->rules(),
3326 auto pf = AMMBid::preflight(pfctx);
3327 BEAST_EXPECT(pf != tesSUCCESS);
3331 auto jtx = env.jt(tx, seq(1), fee(10));
3332 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3333 jtx.jv[
"Asset2"].removeMember(
"issuer");
3334 jtx.stx = env.ust(jtx);
3335 PreflightContext pfctx(
3338 env.current()->rules(),
3341 auto pf = AMMBid::preflight(pfctx);
3342 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3350 testcase(
"Invalid AMM Payment");
3351 using namespace jtx;
3353 using namespace std::literals::chrono_literals;
3357 for (
auto const& acct : {gw, alice})
3361 fund(env, gw, {alice, carol},
XRP(1'000), {USD(100)});
3363 AMM ammAlice(env, acct,
XRP(10), USD(10));
3365 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3368 env(pay(carol, ammAlice.ammAccount(),
XRP(300)),
3371 env(pay(carol, ammAlice.ammAccount(), USD(10)),
3376 fund(env, gw, {alice, carol},
XRP(10'000'000), {USD(10'000)});
3378 AMM ammAlice(env, acct,
XRP(1'000'000), USD(100));
3380 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3383 env(pay(carol, ammAlice.ammAccount(),
XRP(1'000'000)),
3389 testAMM([&](
AMM& ammAlice,
Env& env) {
3399 testAMM([&](
AMM& ammAlice,
Env& env) {
3400 auto const pk = carol.pk();
3401 auto const settleDelay = 100s;
3403 env.
current()->info().parentCloseTime + 200s;
3415 testAMM([&](
AMM& ammAlice,
Env& env) {
3422 [&](
AMM& ammAlice,
Env& env) {
3424 env(pay(alice, carol, USD(100)),
3428 env(pay(alice, carol,
XRP(100)),
3435 STAmount{USD, UINT64_C(99'999999999), -9}),
3441 STAmount{USD, UINT64_C(999'99999999), -8}),
3450 env(pay(alice, carol, USD(99.99)),
3459 {{
XRP(100), USD(100)}});
3462 testAMM([&](
AMM& ammAlice,
Env& env) {
3465 env(pay(alice, carol, USD(1)),
3470 env(pay(alice, carol,
XRP(1)),
3478 testAMM([&](
AMM& ammAlice,
Env& env) {
3484 env(pay(alice, carol, USD(1)),
3489 env(pay(alice, carol,
XRP(1)),
3497 testAMM([&](
AMM& ammAlice,
Env& env) {
3501 env(pay(alice, carol,
XRP(1)),
3512 testcase(
"Basic Payment");
3513 using namespace jtx;
3518 [&](
AMM& ammAlice,
Env& env) {
3519 env.
fund(jtx::XRP(30'000), bob);
3521 env(pay(bob, carol, USD(100)),
3527 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3529 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3532 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3534 {{
XRP(10'000), USD(10'100)}},
3541 [&](
AMM& ammAlice,
Env& env) {
3542 env.
fund(jtx::XRP(30'000), bob);
3544 env(pay(bob, carol, USD(100)),
sendmax(
XRP(100)));
3547 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3549 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3552 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3554 {{
XRP(10'000), USD(10'100)}},
3562 [&](
AMM& ammAlice,
Env& env) {
3563 env.
fund(jtx::XRP(30'000), bob);
3568 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3570 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3573 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3575 {{
XRP(10'000), USD(10'100)}},
3582 [&](
AMM& ammAlice,
Env& env) {
3583 env.
fund(jtx::XRP(30'000), bob);
3587 env(pay(bob, carol, USD(100)),
3594 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3596 BEAST_EXPECT(
expectLine(env, carol, USD(30'010)));
3600 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3604 env(pay(bob, carol, USD(100)),
3612 {{
XRP(10'000), USD(10'010)}},
3619 [&](
AMM& ammAlice,
Env& env) {
3622 env.
fund(jtx::XRP(30'000), bob);
3627 env(pay(bob, carol, USD(100)),
3634 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3639 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
3641 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3643 {{
XRP(10'000), USD(10'010)}},
3650 [&](
AMM& ammAlice,
Env& env) {
3651 env.
fund(jtx::XRP(30'000), bob);
3653 env(pay(bob, carol, USD(100)),
3659 {{
XRP(10'000), USD(10'000)}},
3669 Env env(*
this, features);
3671 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3675 auto ammEUR_XRP =
AMM(env, alice,
XRP(10'000), EUR(10'000));
3676 auto ammUSD_EUR =
AMM(env, alice, EUR(10'000), USD(10'000));
3679 env(pay(bob, carol, USD(100)),
3684 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3686 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3687 ammEUR_XRP.tokens()));
3688 if (!features[fixAMMv1_1])
3690 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3691 STAmount(USD, UINT64_C(9'970'097277662122), -12),
3692 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3693 ammUSD_EUR.tokens()));
3696 Amounts
const expectedAmounts =
3697 env.
closed()->rules().enabled(fixReducedOffersV2)
3698 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233787816), -14)}
3701 STAmount(USD, UINT64_C(29'90272233787818), -14)};
3703 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3707 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3708 STAmount(USD, UINT64_C(9'970'097277662172), -12),
3709 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3710 ammUSD_EUR.tokens()));
3713 Amounts
const expectedAmounts =
3714 env.
closed()->rules().enabled(fixReducedOffersV2)
3715 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233782839), -14)}
3718 STAmount(USD, UINT64_C(29'90272233782840), -14)};
3720 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3736 [&](
AMM& ammAlice,
Env& env) {
3739 env.
trust(EUR(2'000), alice);
3741 env(pay(gw, alice, EUR(1'000)));
3746 env(pay(bob, carol, USD(100)),
3753 STAmount(USD, UINT64_C(9'950'01249687578), -11),
3761 STAmount(EUR, UINT64_C(49'98750312422), -11)},
3763 STAmount(EUR, UINT64_C(49'98750312422), -11),
3764 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
3769 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3786 [&](
AMM& ammAlice,
Env& env) {
3787 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3791 env(pay(alice, carol, USD(200)),
3795 if (!features[fixAMMv1_1])
3798 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3800 BEAST_EXPECT(
expectLine(env, carol, USD(30'200)));
3806 STAmount(USD, UINT64_C(10'000'00000000001), -11),
3811 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
3819 ammCrtFee(env) -
txfee(env, 1)));
3822 {{
XRP(10'000), USD(10'100)}},
3831 Env env(*
this, features);
3832 fund(env, gw, {alice, bob, carol},
XRP(20'000), {USD(2'000)});
3834 AMM ammAlice(env, alice,
XRP(1'000), USD(1'050));
3835 env(pay(alice, carol, USD(200)),
3839 XRP(1'050), USD(1'000), ammAlice.
tokens()));
3840 BEAST_EXPECT(
expectLine(env, carol, USD(2'200)));
3846 [&](
AMM& ammAlice,
Env& env) {
3847 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
3849 env(offer(bob, USD(100),
XRP(100)));
3852 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3854 BEAST_EXPECT(
expectLine(env, bob, USD(1'100)));
3857 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3860 {{
XRP(10'000), USD(10'100)}},
3868 [&](
AMM& ammAlice,
Env& env) {
3869 env(
rate(gw, 1.25));
3875 env(offer(carol, EUR(100), GBP(100)));
3879 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
3881 BEAST_EXPECT(
expectLine(env, carol, GBP(29'875)));
3883 BEAST_EXPECT(
expectLine(env, carol, EUR(30'100)));
3886 {{GBP(1'000), EUR(1'100)}},
3892 [&](
AMM& amm,
Env& env) {
3893 env(
rate(gw, 1.001));
3895 env(offer(carol,
XRP(100), USD(55)));
3897 if (!features[fixAMMv1_1])
3907 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
3909 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
3920 BEAST_EXPECT(amm.expectBalances(
3922 STAmount{USD, UINT64_C(550'000000055), -9},
3931 STAmount{USD, 4'99999995, -8}}}}));
3935 STAmount(USD, UINT64_C(29'949'94999999494), -11));
3938 {{
XRP(1'000), USD(500)}},
3943 [&](
AMM& amm,
Env& env) {
3944 env(
rate(gw, 1.001));
3946 env(offer(carol,
XRP(10), USD(5.5)));
3948 if (!features[fixAMMv1_1])
3950 BEAST_EXPECT(amm.expectBalances(
3952 STAmount{USD, UINT64_C(505'050505050505), -12},
3958 BEAST_EXPECT(amm.expectBalances(
3960 STAmount{USD, UINT64_C(505'0505050505051), -13},
3965 {{
XRP(1'000), USD(500)}},
3971 [&](
AMM& ammAlice,
Env& env) {
3978 {GBP(2'000), EUR(2'000)},
3980 env(
rate(gw, 1.25));
3991 env(offer(carol, EUR(100), GBP(100)));
3993 if (!features[fixAMMv1_1])
4002 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
4003 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
4011 STAmount{EUR, UINT64_C(50'684828792831), -12},
4012 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
4024 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
4029 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
4041 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
4042 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
4050 STAmount{EUR, UINT64_C(27'06583722134028), -14},
4051 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
4063 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
4068 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
4071 BEAST_EXPECT(
expectLine(env, bob, GBP(2'010)));
4073 BEAST_EXPECT(
expectLine(env, ed, EUR(1'987.5)));
4075 {{GBP(1'000), EUR(1'100)}},
4088 [&](
AMM& ammAlice,
Env& env) {
4089 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
4090 env(
rate(gw, 1.25));
4092 env(pay(bob, carol, EUR(100)),
4098 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4100 BEAST_EXPECT(
expectLine(env, carol, EUR(30'080)));
4102 {{GBP(1'000), EUR(1'100)}},
4119 [&](
AMM& ammAlice,
Env& env) {
4122 auto const CAN = gw[
"CAN"];
4123 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
4124 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
4125 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
4126 env(trust(carol, USD(100)));
4127 env(
rate(gw, 1.25));
4129 env(offer(dan, CAN(200), GBP(200)));
4130 env(offer(ed, EUR(200), USD(200)));
4132 env(pay(bob, carol, USD(100)),
4133 path(~GBP, ~EUR, ~USD),
4138 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25), GBP(43.75)));
4140 GBP(10'125), EUR(10'000), ammAlice.
tokens()));
4141 BEAST_EXPECT(
expectLine(env, ed, EUR(300), USD(100)));
4142 BEAST_EXPECT(
expectLine(env, carol, USD(80)));
4144 {{GBP(10'000), EUR(10'125)}},
4151 [&](
AMM& ammAlice,
Env& env) {
4152 env(pay(alice, carol, USD(99.99)),
4157 env(pay(alice, carol, USD(100)),
4162 env(pay(alice, carol,
XRP(100)),
4173 {{
XRP(100), USD(100)}},
4180 Env env(*
this, features);
4181 auto const ETH = gw[
"ETH"];
4187 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4188 fund(env, gw, {carol, bob},
XRP(1'000), {USD(200)}, Fund::Acct);
4189 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4190 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4191 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4192 AMM xrp_usd(env, alice,
XRP(10'150), USD(10'200));
4193 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4194 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4195 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
4196 env(pay(bob, carol, USD(100)),
4197 path(~EUR, ~BTC, ~USD),
4199 path(~ETH, ~EUR, ~USD),
4201 if (!features[fixAMMv1_1])
4207 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
4210 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
4211 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4214 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4215 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4221 STAmount{USD, UINT64_C(10'126'06848287914), -11},
4228 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
4231 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
4232 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4235 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4236 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4242 STAmount{USD, UINT64_C(10'126'06848287943), -11},
4252 BEAST_EXPECT(xrp_eur.expectBalances(
4253 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
4255 EUR(10'000), BTC(10'200), eur_btc.
tokens()));
4257 BTC(10'100), USD(10'000), btc_usd.
tokens()));
4259 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4264 Env env(*
this, features);
4265 auto const ETH = gw[
"ETH"];
4271 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4272 fund(env, gw, {carol, bob},
XRP(1000), {USD(200)}, Fund::Acct);
4273 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4274 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4275 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4276 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4277 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4278 env(pay(bob, carol, USD(100)),
4279 path(~EUR, ~BTC, ~USD),
4280 path(~ETH, ~EUR, ~BTC, ~USD),
4282 if (!features[fixAMMv1_1])
4286 BEAST_EXPECT(xrp_eur.expectBalances(
4288 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
4291 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4292 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4295 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
4300 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
4303 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
4304 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4309 BEAST_EXPECT(xrp_eur.expectBalances(
4311 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
4314 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4315 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4318 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
4323 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
4326 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
4327 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4330 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4336 [&](
AMM& ammAlice,
Env& env) {
4338 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4339 env(trust(alice, EUR(200)));
4340 for (
int i = 0; i < 30; ++i)
4341 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4344 env(offer(alice, EUR(140),
XRP(100)));
4345 env(pay(bob, carol, USD(100)),
4349 if (!features[fixAMMv1_1])
4354 STAmount{USD, UINT64_C(9'970'089730807577), -12},
4359 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
4365 STAmount{USD, UINT64_C(9'970'089730807827), -12},
4370 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
4381 [&](
AMM& ammAlice,
Env& env) {
4383 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4384 env(trust(alice, EUR(200)));
4385 for (
int i = 0; i < 29; ++i)
4386 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4389 env(offer(alice, EUR(140),
XRP(100)));
4390 env(pay(bob, carol, USD(100)),
4396 if (!features[fixAMMv1_1])
4402 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4406 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4412 {{{
STAmount{EUR, UINT64_C(39'1858572), -7},
4423 Env env(*
this, features);
4424 fund(env, gw, {alice, carol, bob},
XRP(30'000), {USD(30'000)});
4425 env(offer(bob,
XRP(100), USD(100.001)));
4426 AMM ammAlice(env, alice,
XRP(10'000), USD(10'100));
4427 env(offer(carol, USD(100),
XRP(100)));
4428 if (!features[fixAMMv1_1])
4432 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4439 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
4445 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4452 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
4453 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4459 [&](
AMM& ammAlice,
Env& env) {
4463 env(pay(alice, carol, USD(1)),
4478 testcase(
"AMM Tokens");
4479 using namespace jtx;
4482 testAMM([&](
AMM& ammAlice,
Env& env) {
4483 auto const token1 = ammAlice.
lptIssue();
4490 env(offer(carol,
STAmount{token1, 5'000'000}, priceXRP));
4492 env(offer(alice, priceXRP,
STAmount{token1, 5'000'000}));
4500 ammAlice.
vote(carol, 1'000);
4502 ammAlice.
vote(carol, 0);
4505 env(ammAlice.
bid({.account = carol, .bidMin = 100}));
4526 testAMM([&](
AMM& ammAlice,
Env& env) {
4527 ammAlice.
deposit(carol, 1'000'000);
4528 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
4529 AMM ammAlice1(env, alice,
XRP(10'000), EUR(10'000));
4530 ammAlice1.deposit(carol, 1'000'000);
4531 auto const token1 = ammAlice.
lptIssue();
4532 auto const token2 = ammAlice1.lptIssue();
4552 testAMM([&](
AMM& ammAlice,
Env& env) {
4553 auto const token1 = ammAlice.
lptIssue();
4556 ammAlice.
deposit(carol, 1'000'000);
4562 env(pay(alice, carol,
STAmount{token1, 100}));
4572 env(pay(carol, alice,
STAmount{token1, 100}));
4584 testcase(
"Amendment");
4585 using namespace jtx;
4590 all - featureAMM - fixUniversalNumber};
4592 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
4594 Env env{*
this, feature};
4595 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
4613 using namespace jtx;
4615 testAMM([&](
AMM& ammAlice,
Env& env) {
4616 auto const info = env.
rpc(
4620 "{\"account\": \"" + to_string(ammAlice.
ammAccount()) +
4623 info[jss::result][jss::account_data][jss::Flags].asUInt();
4633 testcase(
"Rippling");
4634 using namespace jtx;
4650 auto const TSTA = A[
"TST"];
4651 auto const TSTB = B[
"TST"];
4660 env.
trust(TSTA(10'000), C);
4661 env.
trust(TSTB(10'000), C);
4662 env(pay(A, C, TSTA(10'000)));
4663 env(pay(B, C, TSTB(10'000)));
4664 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
4665 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
4672 env(pay(C, D,
STAmount{ammIss, 10}),
4674 path(amm.ammAccount()),
4683 testcase(
"AMMAndCLOB, offer quality change");
4684 using namespace jtx;
4685 auto const gw =
Account(
"gw");
4686 auto const TST = gw[
"TST"];
4687 auto const LP1 =
Account(
"LP1");
4688 auto const LP2 =
Account(
"LP2");
4690 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4691 Env env(*
this, features);
4692 env.
fund(
XRP(30'000'000'000), gw);
4693 env(offer(gw,
XRP(11'500'000'000), TST(1'000'000'000)));
4697 env(offer(LP1, TST(25),
XRPAmount(287'500'000)));
4702 env(offer(LP2, TST(25),
XRPAmount(287'500'000)));
4714 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
4720 lp2TakerGets = offer[
"taker_gets"].asString();
4721 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
4726 if (!features[fixAMMv1_1])
4730 STAmount{TST, UINT64_C(1'68737984885388), -14}),
4736 STAmount{TST, UINT64_C(1'68737976189735), -14}),
4745 BEAST_EXPECT(lp2TakerGets == offer[
"taker_gets"].asString());
4747 lp2TakerPays == offer[
"taker_pays"][
"value"].asString());
4754 testcase(
"Trading Fee");
4755 using namespace jtx;
4759 [&](
AMM& ammAlice,
Env& env) {
4761 ammAlice.
deposit(carol, USD(3'000));
4765 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4767 ammAlice.
vote(alice, 1'000);
4771 ammAlice.
deposit(carol, USD(3'000));
4773 carol,
IOUAmount{994'981155689671, -12}));
4774 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4776 ammAlice.
vote(alice, 0);
4782 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
4784 {{USD(1'000), EUR(1'000)}},
4792 [&](
AMM& ammAlice,
Env& env) {
4794 auto tokensFee = ammAlice.
deposit(
4795 carol, USD(1'000), std::nullopt,
STAmount{USD, 1, -1});
4798 ammAlice.
vote(alice, 0);
4800 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4803 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4804 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4814 [&](
AMM& ammAlice,
Env& env) {
4816 auto const tokensFee = ammAlice.
deposit(
4817 carol, USD(200), std::nullopt,
STAmount{USD, 2020, -6});
4820 ammAlice.
vote(alice, 0);
4822 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4825 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4826 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4835 [&](
AMM& ammAlice,
Env& env) {
4837 ammAlice.
deposit(carol, USD(3'000));
4840 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4842 ammAlice.
vote(alice, 1'000);
4849 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
4851 {{USD(1'000), EUR(1'000)}},
4858 [&](
AMM& ammAlice,
Env& env) {
4859 ammAlice.
deposit(carol, 1'000'000);
4860 auto const tokensFee = ammAlice.
withdraw(
4861 carol, USD(100), std::nullopt,
IOUAmount{520, 0});
4863 auto const balanceAfterWithdraw = [&]() {
4864 if (!features[fixAMMv1_1])
4865 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
4866 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
4868 BEAST_EXPECT(env.
balance(carol, USD) == balanceAfterWithdraw);
4870 auto const deposit = balanceAfterWithdraw - USD(29'000);
4871 ammAlice.
deposit(carol, deposit);
4873 ammAlice.
vote(alice, 0);
4875 auto const tokensNoFee = ammAlice.
withdraw(carol, deposit);
4876 if (!features[fixAMMv1_1])
4879 STAmount(USD, UINT64_C(30'443'43891402717), -11));
4883 STAmount(USD, UINT64_C(30'443'43891402716), -11));
4886 if (!features[fixAMMv1_1])
4888 tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4891 tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4892 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4901 [&](
AMM& ammAlice,
Env& env) {
4907 {USD(1'000), EUR(1'000)},
4910 BEAST_EXPECT(
expectLine(env, alice, EUR(28'990)));
4911 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4912 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4914 env(pay(carol, alice, EUR(10)),
4920 BEAST_EXPECT(
expectLine(env, alice, EUR(29'000)));
4921 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4922 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4925 ammAlice.
vote(alice, 1'000);
4928 env(pay(bob, carol, USD(10)),
4935 env, bob,
STAmount{EUR, UINT64_C(989'8989898989899), -13}));
4937 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4940 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
4943 {{USD(1'000), EUR(1'010)}},
4950 [&](
AMM& ammAlice,
Env& env) {
4952 env(offer(carol, EUR(10), USD(10)));
4954 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4955 BEAST_EXPECT(
expectLine(env, carol, EUR(30'010)));
4957 env(offer(carol, USD(10), EUR(10)));
4960 ammAlice.
vote(alice, 500);
4962 env(offer(carol, EUR(10), USD(10)));
4969 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
4973 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
4979 STAmount{EUR, UINT64_C(5'025125628140703), -15},
4980 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
4981 if (!features[fixAMMv1_1])
4984 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4985 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4991 STAmount{USD, UINT64_C(1'004'97487437186), -11},
4992 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
4996 {{USD(1'000), EUR(1'010)}},
5006 Env env(*
this, features);
5011 {alice, bob, carol, ed},
5013 {USD(2'000), EUR(2'000)});
5014 env(offer(carol, EUR(5), USD(5)));
5015 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
5016 env(pay(bob, ed, USD(10)),
5020 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5021 if (!features[fixAMMv1_1])
5023 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5025 USD(1'000), EUR(1'005), ammAlice.
tokens()));
5030 env, bob,
STAmount(EUR, UINT64_C(1989'999999999999), -12)));
5033 STAmount(EUR, UINT64_C(1005'000000000001), -12),
5042 Env env(*
this, features);
5047 {alice, bob, carol, ed},
5049 {USD(2'000), EUR(2'000)});
5050 env(offer(carol, EUR(5), USD(5)));
5052 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 250);
5053 env(pay(bob, ed, USD(10)),
5057 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5058 if (!features[fixAMMv1_1])
5063 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
5066 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
5074 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
5077 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
5087 Env env(*
this, features);
5092 {alice, bob, carol, ed},
5094 {USD(2'000), EUR(2'000)});
5095 env(offer(carol, EUR(10), USD(10)));
5097 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5098 env(pay(bob, ed, USD(10)),
5102 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5103 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5105 USD(1'005), EUR(1'000), ammAlice.
tokens()));
5114 Env env(*
this, features);
5119 {alice, bob, carol, ed},
5121 {USD(2'000), EUR(2'000)});
5122 env(offer(carol, EUR(9), USD(9)));
5124 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5125 env(pay(bob, ed, USD(10)),
5129 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5131 env, bob,
STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
5134 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
5143 testcase(
"Adjusted Deposit/Withdraw Tokens");
5145 using namespace jtx;
5149 [&](
AMM& ammAlice,
Env& env) {
5157 Account const nataly(
"nataly");
5161 {bob, ed, paul, dan, chris, simon, ben, nataly},
5164 for (
int i = 0; i < 10; ++i)
5168 ammAlice.
deposit(simon, USD(0.1));
5170 ammAlice.
deposit(chris, USD(1));
5172 ammAlice.
deposit(dan, USD(10));
5174 ammAlice.
deposit(bob, USD(100));
5176 ammAlice.
deposit(carol, USD(1'000));
5178 ammAlice.
deposit(ed, USD(10'000));
5180 ammAlice.
deposit(paul, USD(100'000));
5182 ammAlice.
deposit(nataly, USD(1'000'000));
5188 if (!features[fixAMMv1_1])
5191 STAmount{USD, UINT64_C(10'000'0000000013), -10},
5196 BEAST_EXPECT(
expectLine(env, ben, USD(1'500'000)));
5197 BEAST_EXPECT(
expectLine(env, simon, USD(1'500'000)));
5198 BEAST_EXPECT(
expectLine(env, chris, USD(1'500'000)));
5199 BEAST_EXPECT(
expectLine(env, dan, USD(1'500'000)));
5200 if (!features[fixAMMv1_1])
5204 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
5206 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5207 BEAST_EXPECT(
expectLine(env, ed, USD(1'500'000)));
5208 BEAST_EXPECT(
expectLine(env, paul, USD(1'500'000)));
5209 if (!features[fixAMMv1_1])
5213 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
5218 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
5221 if (!features[fixAMMv1_1])
5225 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
5227 BEAST_EXPECT(
expectLine(env, alice, USD(30'000)));
5238 testAMM([&](
AMM& ammAlice,
Env& env) {
5246 Account const nataly(
"nataly");
5250 {bob, ed, paul, dan, chris, simon, ben, nataly},
5254 for (
int i = 0; i < 10; ++i)
5281 auto const xrpBalance = (
XRP(2'000'000) -
txfee(env, 20)).getText();
5299 testcase(
"Auto Delete");
5301 using namespace jtx;
5312 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5313 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5318 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5323 amm.withdrawAll(gw);
5324 BEAST_EXPECT(amm.ammExists());
5349 env(trust(alice,
STAmount{amm.lptIssue(), 10'000}),
5364 amm.expectBalances(
XRP(10'000), USD(10'000), amm.tokens()));
5365 BEAST_EXPECT(amm.expectTradingFee(1'000));
5366 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5370 amm.withdrawAll(alice);
5371 BEAST_EXPECT(!amm.ammExists());
5372 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5383 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5384 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5389 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5393 amm.withdrawAll(gw);
5394 BEAST_EXPECT(amm.ammExists());
5398 BEAST_EXPECT(amm.ammExists());
5400 amm.ammDelete(alice);
5401 BEAST_EXPECT(!amm.ammExists());
5402 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5412 testcase(
"Clawback");
5413 using namespace jtx;
5417 AMM amm(env, gw,
XRP(1'000), USD(1'000));
5425 using namespace jtx;
5426 testAMM([&](
AMM& amm,
Env& env) {
5427 amm.setClose(
false);
5428 auto const info = env.
rpc(
5432 "{\"account\": \"" + to_string(amm.ammAccount()) +
"\"}"));
5436 info[jss::result][jss::account_data][jss::AMMID]
5437 .asString() == to_string(amm.ammID()));
5443 amm.deposit(carol, 1'000);
5444 auto affected = env.
meta()->getJson(
5445 JsonOptions::none)[sfAffectedNodes.fieldName];
5449 for (
auto const& node : affected)
5451 if (node.isMember(sfModifiedNode.fieldName) &&
5452 node[sfModifiedNode.fieldName]
5453 [sfLedgerEntryType.fieldName]
5454 .asString() ==
"AccountRoot" &&
5455 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
5457 .asString() == to_string(amm.ammAccount()))
5459 found = node[sfModifiedNode.fieldName]
5460 [sfFinalFields.fieldName][jss::AMMID]
5461 .asString() == to_string(amm.ammID());
5465 BEAST_EXPECT(found);
5477 testcase(
"Offer/Strand Selection");
5478 using namespace jtx;
5481 auto const ETH = gw1[
"ETH"];
5482 auto const CAN = gw1[
"CAN"];
5488 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5489 fund(env, gw, {alice, carol, bob, ed},
XRP(2'000), {USD(2'000)});
5494 {alice, carol, bob, ed},
5495 {ETH(2'000), CAN(2'000)},
5497 env(
rate(gw, gwRate));
5498 env(
rate(gw1, gw1Rate));
5502 for (
auto const& rates :
5517 for (
auto i = 0; i < 3; ++i)
5519 Env env(*
this, features);
5520 prep(env, rates.first, rates.second);
5522 if (i == 0 || i == 2)
5528 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5529 env(pay(carol, bob, USD(100)),
5536 BEAST_EXPECT(amm->expectBalances(
5537 USD(1'000), ETH(1'000), amm->tokens()));
5539 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5540 q[i] = Quality(Amounts{
5541 ETH(2'000) - env.
balance(carol, ETH),
5542 env.
balance(bob, USD) - USD(2'000)});
5545 BEAST_EXPECT(q[0] > q[1]);
5547 BEAST_EXPECT(q[0] == q[2]);
5554 for (
auto i = 0; i < 3; ++i)
5556 Env env(*
this, features);
5557 prep(env, rates.first, rates.second);
5559 if (i == 0 || i == 2)
5565 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5566 env(offer(alice, USD(400), ETH(400)));
5571 BEAST_EXPECT(amm->expectBalances(
5572 USD(1'000), ETH(1'000), amm->tokens()));
5574 if (i == 0 || i == 2)
5583 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
5594 for (
auto i = 0; i < 3; ++i)
5596 Env env(*
this, features);
5597 prep(env, rates.first, rates.second);
5599 if (i == 0 || i == 2)
5605 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5606 env(pay(carol, bob, USD(100)),
5613 BEAST_EXPECT(!amm->expectBalances(
5614 USD(1'000), ETH(1'000), amm->tokens()));
5616 if (i == 2 && !features[fixAMMv1_1])
5618 if (rates.first == 1.5)
5620 if (!features[fixAMMv1_1])
5628 UINT64_C(378'6327949540823),
5632 UINT64_C(283'9745962155617),
5642 UINT64_C(378'6327949540813),
5646 UINT64_C(283'974596215561),
5651 if (!features[fixAMMv1_1])
5659 UINT64_C(325'299461620749),
5663 UINT64_C(243'9745962155617),
5673 UINT64_C(325'299461620748),
5677 UINT64_C(243'974596215561),
5683 if (rates.first == 1.5)
5691 ETH, UINT64_C(378'6327949540812), -13},
5694 UINT64_C(283'9745962155609),
5705 ETH, UINT64_C(325'2994616207479), -13},
5708 UINT64_C(243'9745962155609),
5712 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5713 q[i] = Quality(Amounts{
5714 ETH(2'000) - env.
balance(carol, ETH),
5715 env.
balance(bob, USD) - USD(2'000)});
5718 BEAST_EXPECT(q[1] > q[0]);
5720 BEAST_EXPECT(q[2] > q[1]);
5724 for (
auto i = 0; i < 3; ++i)
5726 Env env(*
this, features);
5727 prep(env, rates.first, rates.second);
5729 if (i == 0 || i == 2)
5735 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5736 env(offer(alice, USD(250), ETH(400)));
5741 BEAST_EXPECT(!amm->expectBalances(
5742 USD(1'000), ETH(1'000), amm->tokens()));
5748 if (rates.first == 1.5)
5750 if (!features[fixAMMv1_1])
5753 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
5760 USD, UINT64_C(40'5694150420947), -13},
5762 ETH, UINT64_C(64'91106406735152), -14},
5776 ETH, UINT64_C(335'0889359326475), -13},
5778 USD, UINT64_C(209'4305849579047), -13},
5785 if (!features[fixAMMv1_1])
5794 ETH, UINT64_C(335'0889359326485), -13},
5796 USD, UINT64_C(209'4305849579053), -13},
5809 ETH, UINT64_C(335'0889359326475), -13},
5811 USD, UINT64_C(209'4305849579047), -13},
5837 for (
auto i = 0; i < 3; ++i)
5839 Env env(*
this, features);
5840 prep(env, rates.first, rates.second);
5843 if (i == 0 || i == 2)
5851 amm.emplace(env, ed, ETH(1'000), USD(1'000));
5853 env(pay(carol, bob, USD(100)),
5859 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5861 if (i == 2 && !features[fixAMMv1_1])
5863 if (rates.first == 1.5)
5866 BEAST_EXPECT(amm->expectBalances(
5867 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
5873 BEAST_EXPECT(amm->expectBalances(
5875 ETH, UINT64_C(1'179'540094339627), -12},
5876 STAmount{USD, UINT64_C(847'7880529867501), -13},
5885 UINT64_C(343'3179205198749),
5889 UINT64_C(343'3179205198749),
5895 UINT64_C(362'2119470132499),
5899 UINT64_C(362'2119470132499),
5906 if (rates.first == 1.5)
5909 BEAST_EXPECT(amm->expectBalances(
5911 ETH, UINT64_C(1'176'660389557593), -12},
5917 BEAST_EXPECT(amm->expectBalances(
5918 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
5919 STAmount{USD, UINT64_C(847'7880529867501), -13},
5928 UINT64_C(343'3179205198749),
5932 UINT64_C(343'3179205198749),
5938 UINT64_C(362'2119470132499),
5942 UINT64_C(362'2119470132499),
5947 q[i] = Quality(Amounts{
5948 ETH(2'000) - env.
balance(carol, ETH),
5949 env.
balance(bob, USD) - USD(2'000)});
5951 BEAST_EXPECT(q[1] > q[0]);
5952 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5960 testcase(
"Fix Default Inner Object");
5961 using namespace jtx;
5972 Env env(*
this, features);
5973 fund(env, gw, {alice},
XRP(1'000), {USD(10)});
5979 {.tfee = tfee, .close = closeLedger});
5980 amm.deposit(alice, USD(10),
XRP(10));
5983 .
account = gw, .asset1Out = USD(1), .err =
ter(err2)});
5991 .
account = gw, .asset1Out = USD(2), .err =
ter(err4)});
5998 all - fixInnerObjTemplate,
6013 all - fixInnerObjTemplate,
6025 all - fixInnerObjTemplate,
6034 all - fixInnerObjTemplate,
6048 all - fixInnerObjTemplate,
6060 testcase(
"Fix changeSpotPriceQuality");
6061 using namespace jtx;
6064 SucceedShouldSucceedResize,
6076 auto const xrpIouAmounts10_100 =
6078 auto const iouXrpAmounts10_100 =
6083 {
"0.001519763260828713",
"1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
6084 {
"0.01099814367603737",
"1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
6085 {
"0.78",
"796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
6086 {
"105439.2955578965",
"49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
6087 {
"12408293.23445213",
"4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
6088 {
"1892611",
"0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
6089 {
"423028.8508101858",
"3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
6090 {
"44565388.41001027",
"73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
6091 {
"66831.68494832662",
"16", Quality{6346111134641742975}, 0, FailShouldSucceed},
6092 {
"675.9287302203422",
"1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
6093 {
"7047.112186735699",
"1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
6094 {
"840236.4402981238",
"47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
6095 {
"992715.618909774",
"189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
6096 {
"504636667521",
"185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
6097 {
"992706.7218636649",
"189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
6098 {
"1.068737911388205",
"127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
6099 {
"17932506.56880419",
"189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
6100 {
"1.066379294658174",
"128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
6101 {
"350131413924",
"1576879.110907892", Quality{6487411636539049449}, 650,
Fail},
6102 {
"422093460",
"2.731797662057464", Quality{6702911108534394924}, 1000,
Fail},
6103 {
"76128132223",
"367172.7148422662", Quality{6487263463413514240}, 548,
Fail},
6104 {
"132701839250",
"280703770.7695443", Quality{6273750681188885075}, 562,
Fail},
6105 {
"994165.7604612011",
"189551302411", Quality{5697835592690668727}, 815,
Fail},
6106 {
"45053.33303227917",
"86612695359", Quality{5625695218943638190}, 500,
Fail},
6107 {
"199649.077043865",
"14017933007", Quality{5766034667318524880}, 324,
Fail},
6108 {
"27751824831.70903",
"78896950", Quality{6272538159621630432}, 500,
Fail},
6109 {
"225.3731275781907",
"156431793648", Quality{5477818047604078924}, 989,
Fail},
6110 {
"199649.077043865",
"14017933007", Quality{5766036094462806309}, 324,
Fail},
6111 {
"3.590272027140361",
"20677643641", Quality{5406056147042156356}, 808,
Fail},
6112 {
"1.070884664490231",
"127604712776", Quality{5268620608623825741}, 293,
Fail},
6113 {
"3272.448829820197",
"6275124076", Quality{5625710328924117902}, 81,
Fail},
6114 {
"0.009059512633902926",
"7994028", Quality{5477511954775533172}, 1000,
Fail},
6115 {
"1",
"1.0", Quality{0}, 100,
Fail},
6116 {
"1.0",
"1", Quality{0}, 100,
Fail},
6117 {
"10",
"10.0", Quality{xrpIouAmounts10_100}, 100,
Fail},
6118 {
"10.0",
"10", Quality{iouXrpAmounts10_100}, 100,
Fail},
6119 {
"69864389131",
"287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
6120 {
"4328342973",
"12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
6121 {
"32347017",
"7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
6122 {
"61697206161",
"36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
6123 {
"1654524979",
"7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
6124 {
"88621.22277293179",
"5128418948", Quality{5766347291552869205}, 380, Succeed},
6125 {
"1892611",
"0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
6126 {
"4542.639373338766",
"24554809", Quality{5838994982188783710}, 0, Succeed},
6127 {
"5132932546",
"88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
6128 {
"78929964.1549083",
"1506494795", Quality{5986890029845558688}, 589, Succeed},
6129 {
"10096561906",
"44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
6130 {
"5092.219565514988",
"8768257694", Quality{5626349534958379008}, 503, Succeed},
6131 {
"1819778294",
"8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
6132 {
"6970462.633911943",
"57359281", Quality{6054087899185946624}, 850, Succeed},
6133 {
"3983448845",
"2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
6137 {
"771493171",
"1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
6141 boost::regex rx(
"^\\d+$");
6142 boost::smatch match;
6145 Env env(*
this, features);
6146 auto rules = env.
current()->rules();
6148 for (
auto const& t : tests)
6155 auto const& quality = std::get<Quality>(t);
6156 auto const tfee = std::get<std::uint16_t>(t);
6157 auto const status = std::get<Status>(t);
6158 auto const poolInIsXRP =
6159 boost::regex_search(std::get<0>(t), match, rx);
6160 auto const poolOutIsXRP =
6161 boost::regex_search(std::get<1>(t), match, rx);
6162 assert(!(poolInIsXRP && poolOutIsXRP));
6163 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
6164 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
6168 Amounts{poolIn, poolOut},
6175 if (status == SucceedShouldSucceedResize)
6177 if (!features[fixAMMv1_1])
6178 BEAST_EXPECT(Quality{*amounts} < quality);
6180 BEAST_EXPECT(Quality{*amounts} >= quality);
6182 else if (status == Succeed)
6184 if (!features[fixAMMv1_1])
6186 Quality{*amounts} >= quality ||
6188 Quality{*amounts}, quality,
Number{1, -7}));
6190 BEAST_EXPECT(Quality{*amounts} >= quality);
6192 else if (status == FailShouldSucceed)
6195 features[fixAMMv1_1] &&
6196 Quality{*amounts} >= quality);
6198 else if (status == SucceedShouldFail)
6201 !features[fixAMMv1_1] &&
6202 Quality{*amounts} < quality &&
6204 Quality{*amounts}, quality,
Number{1, -7}));
6213 if (status ==
Fail && quality != Quality{0})
6215 auto tinyOffer = [&]() {
6222 Amounts{poolIn, poolOut},
6226 else if (
isXRP(poolOut))
6231 Amounts{poolIn, poolOut},
6236 auto const takerPays = toAmount<STAmount>(
6241 Amounts{poolIn, poolOut}, takerPays, tfee)};
6243 BEAST_EXPECT(Quality(tinyOffer) < quality);
6245 else if (status == FailShouldSucceed)
6247 BEAST_EXPECT(!features[fixAMMv1_1]);
6249 else if (status == SucceedShouldFail)
6251 BEAST_EXPECT(features[fixAMMv1_1]);
6258 !strcmp(e.
what(),
"changeSpotPriceQuality failed"));
6260 !features[fixAMMv1_1] && status == FailShouldSucceed);
6269 BEAST_EXPECT(!res.has_value());
6276 using namespace jtx;
6278 testAMM([&](
AMM& ammAlice,
Env& env) {
6286 testAMM([&](
AMM& ammAlice,
Env& env) {
6294 testAMM([&](
AMM& ammAlice,
Env& env) {
6302 testAMM([&](
AMM& ammAlice,
Env& env) {
6305 .asset2Out =
XRP(100),
6311 testAMM([&](
AMM& ammAlice,
Env& env) {
6314 .asset2Out = BAD(100),
6320 testAMM([&](
AMM& ammAlice,
Env& env) {
6322 jv[jss::TransactionType] = jss::AMMWithdraw;
6324 jv[jss::Account] = alice.human();
6326 XRP(100).value().setJson(jv[jss::Amount]);
6327 USD(100).value().setJson(jv[jss::EPrice]);
6335 using namespace jtx;
6339 Account const gatehub{
"gatehub"};
6340 Account const bitstamp{
"bitstamp"};
6341 Account const trader{
"trader"};
6342 auto const usdGH = gatehub[
"USD"];
6343 auto const btcGH = gatehub[
"BTC"];
6344 auto const usdBIT = bitstamp[
"USD"];
6348 char const* testCase;
6349 double const poolUsdBIT;
6350 double const poolUsdGH;
6362 double const offer1BtcGH = 0.1;
6363 double const offer2BtcGH = 0.1;
6364 double const offer2UsdGH = 1;
6365 double const rateBIT = 0.0;
6366 double const rateGH = 0.0;
6371 for (
auto const& input : {
6373 .testCase =
"Test Fix Overflow Offer",
6376 .sendMaxUsdBIT{usdBIT(50)},
6377 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6380 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6381 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6382 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6383 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6384 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6385 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6386 .lpTokenBalance = {28'61817604250837, -14},
6394 .testCase =
"Overflow test {1, 100, 0.111}",
6397 .sendMaxUsdBIT{usdBIT(0.111)},
6398 .sendUsdGH{usdGH, 100},
6401 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6402 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6403 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6404 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6405 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6406 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6407 .lpTokenBalance{10, 0},
6408 .offer1BtcGH = 1e-5,
6410 .offer2UsdGH = 1e-5,
6415 .testCase =
"Overflow test {1, 100, 1.00}",
6418 .sendMaxUsdBIT{usdBIT(1.00)},
6419 .sendUsdGH{usdGH, 100},
6422 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6423 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6424 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6425 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6426 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6427 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6428 .lpTokenBalance{10, 0},
6429 .offer1BtcGH = 1e-5,
6431 .offer2UsdGH = 1e-5,
6436 .testCase =
"Overflow test {1, 100, 4.6432}",
6439 .sendMaxUsdBIT{usdBIT(4.6432)},
6440 .sendUsdGH{usdGH, 100},
6443 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6444 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6445 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6446 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6447 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6448 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6449 .lpTokenBalance{10, 0},
6450 .offer1BtcGH = 1e-5,
6452 .offer2UsdGH = 1e-5,
6457 .testCase =
"Overflow test {1, 100, 10}",
6460 .sendMaxUsdBIT{usdBIT(10)},
6461 .sendUsdGH{usdGH, 100},
6464 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6465 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6466 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6467 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6468 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6469 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6470 .lpTokenBalance{10, 0},
6471 .offer1BtcGH = 1e-5,
6473 .offer2UsdGH = 1e-5,
6478 .testCase =
"Overflow test {50, 100, 5.55}",
6481 .sendMaxUsdBIT{usdBIT(5.55)},
6482 .sendUsdGH{usdGH, 100},
6485 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6486 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6487 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6488 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6489 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6490 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6491 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6492 .offer1BtcGH = 1e-5,
6494 .offer2UsdGH = 1e-5,
6499 .testCase =
"Overflow test {50, 100, 50.00}",
6502 .sendMaxUsdBIT{usdBIT(50.00)},
6503 .sendUsdGH{usdGH, 100},
6504 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6505 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6506 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6507 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6508 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6509 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6510 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6511 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6512 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6513 .offer1BtcGH = 1e-5,
6515 .offer2UsdGH = 1e-5,
6520 .testCase =
"Overflow test {50, 100, 232.16}",
6523 .sendMaxUsdBIT{usdBIT(232.16)},
6524 .sendUsdGH{usdGH, 100},
6527 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6528 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6529 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6530 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6531 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6532 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6533 .lpTokenBalance{70'71067811865475, -14},
6534 .offer1BtcGH = 1e-5,
6536 .offer2UsdGH = 1e-5,
6541 .testCase =
"Overflow test {50, 100, 500}",
6544 .sendMaxUsdBIT{usdBIT(500)},
6545 .sendUsdGH{usdGH, 100},
6548 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6549 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6550 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6551 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6552 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6553 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6554 .lpTokenBalance{70'71067811865475, -14},
6555 .offer1BtcGH = 1e-5,
6557 .offer2UsdGH = 1e-5,
6563 testcase(input.testCase);
6564 for (
auto const& features :
6565 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
6567 Env env(*
this, features);
6569 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6572 if (input.rateGH != 0.0)
6573 env(
rate(gatehub, input.rateGH));
6574 if (input.rateBIT != 0.0)
6575 env(
rate(bitstamp, input.rateBIT));
6577 env(trust(trader, usdGH(10'000'000)));
6578 env(trust(trader, usdBIT(10'000'000)));
6579 env(trust(trader, btcGH(10'000'000)));
6582 env(pay(gatehub, trader, usdGH(100'000)));
6583 env(pay(gatehub, trader, btcGH(100'000)));
6584 env(pay(bitstamp, trader, usdBIT(100'000)));
6590 usdGH(input.poolUsdGH),
6591 usdBIT(input.poolUsdBIT)};
6595 amm.getLPTokensBalance();
6597 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6600 btcGH(input.offer2BtcGH),
6601 usdGH(input.offer2UsdGH)));
6604 env(pay(trader, trader, input.sendUsdGH),
6606 path(~btcGH, ~usdGH),
6611 auto const failUsdGH =
6612 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6613 auto const failUsdBIT =
6614 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6615 auto const goodUsdGH =
6616 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6617 auto const goodUsdBIT =
6618 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6619 if (!features[fixAMMOverflowOffer])
6621 BEAST_EXPECT(amm.expectBalances(
6622 failUsdGH, failUsdBIT, input.lpTokenBalance));
6626 BEAST_EXPECT(amm.expectBalances(
6627 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
6632 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6636 Number const sqrtPoolProduct =
6637 root2(goodUsdGH * goodUsdBIT);
6648 (sqrtPoolProduct +
Number{1, -14} >=
6649 input.lpTokenBalance));
6658 testcase(
"swapRounding");
6659 using namespace jtx;
6661 const STAmount xrpPool{
XRP, UINT64_C(51600'000981)};
6662 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
6664 const STAmount xrpBob{
XRP, UINT64_C(1092'878933)};
6666 USD, UINT64_C(3'988035892323031), -28};
6669 [&](
AMM& amm,
Env& env) {
6671 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(
XRP, USD);
6674 env.
fund(xrpBob, bob);
6675 env.
trust(USD(1'000'000), bob);
6676 env(pay(gw, bob, iouBob));
6679 env(offer(bob,
XRP(6300), USD(100'000)));
6684 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
6686 {{xrpPool, iouPool}},
6689 {jtx::supported_amendments() | fixAMMv1_1});
6695 testcase(
"AMM Offer Blocked By LOB");
6696 using namespace jtx;
6702 Env env(*
this, features);
6704 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6706 env(offer(alice,
XRP(1), USD(0.01)));
6709 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6713 env(offer(carol, USD(0.49),
XRP(1)));
6716 if (!features[fixAMMv1_1])
6718 BEAST_EXPECT(amm.expectBalances(
6719 XRP(200'000), USD(100'000), amm.tokens()));
6721 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6724 env, carol, 1, {{Amounts{USD(0.49),
XRP(1)}}}));
6728 BEAST_EXPECT(amm.expectBalances(
6729 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6731 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6740 Env env(*
this, features);
6742 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6746 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6749 env(offer(carol, USD(0.49),
XRP(1)));
6753 BEAST_EXPECT(amm.expectBalances(
6754 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6761 Env env(*
this, features);
6762 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6766 env(offer(bob, USD(1),
XRPAmount(500)));
6768 AMM amm(env, alice,
XRP(1'000), USD(500));
6769 env(offer(carol,
XRP(100), USD(55)));
6771 if (!features[fixAMMv1_1])
6774 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
6776 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6778 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
6782 BEAST_EXPECT(amm.expectBalances(
6784 STAmount{USD, UINT64_C(550'000000055), -9},
6792 STAmount{USD, 4'99999995, -8}}}}));
6794 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6801 Env env(*
this, features);
6802 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6804 AMM amm(env, alice,
XRP(1'000), USD(500));
6805 env(offer(carol,
XRP(100), USD(55)));
6807 BEAST_EXPECT(amm.expectBalances(
6809 STAmount{USD, UINT64_C(550'000000055), -9},
6823 using namespace jtx;
6827 Env env(*
this, features);
6833 {USD(1'000'000'000)});
6834 AMM amm(env, gw,
XRP(2), USD(1));
6835 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6836 amm.deposit(carol,
IOUAmount{1'000'000});
6837 amm.withdrawAll(alice);
6838 amm.withdrawAll(carol);
6840 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
6841 auto const lpTokenBalance =
6842 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6844 lpToken ==
"1414.213562373095" &&
6845 lpTokenBalance ==
"1414.213562373");
6846 if (!features[fixAMMv1_1])
6849 BEAST_EXPECT(amm.ammExists());
6853 amm.withdrawAll(gw);
6854 BEAST_EXPECT(!amm.ammExists());
6860 for (
auto const& lp : {gw, bob})
6862 Env env(*
this, features);
6863 auto const ABC = gw[
"ABC"];
6867 {alice, carol, bob},
6869 {USD(1'000'000'000), ABC(1'000'000'000'000)});
6870 AMM amm(env, lp, ABC(2'000'000), USD(1));
6871 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6872 amm.deposit(carol,
IOUAmount{1'000'000});
6873 amm.withdrawAll(alice);
6874 amm.withdrawAll(carol);
6876 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
6877 auto const lpTokenBalance =
6878 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6880 lpToken ==
"1414.213562373095" &&
6881 lpTokenBalance ==
"1414.213562373");
6882 if (!features[fixAMMv1_1])
6885 BEAST_EXPECT(amm.ammExists());
6889 amm.withdrawAll(lp);
6890 BEAST_EXPECT(!amm.ammExists());
6897 Env env(*
this, features);
6898 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)});
6899 AMM amm(env, gw,
XRP(10), USD(10));
6900 amm.deposit(alice, 1'000);
6903 BEAST_EXPECT(res && !res.value());
6906 BEAST_EXPECT(res && !res.value());
6910 Env env(*
this, features);
6911 fund(env, gw, {alice},
XRP(1'000), {USD(1'000), EUR(1'000)});
6912 AMM amm(env, gw, EUR(10), USD(10));
6913 amm.deposit(alice, 1'000);
6916 BEAST_EXPECT(res && !res.value());
6919 BEAST_EXPECT(res && !res.value());
6923 Env env(*
this, features);
6925 auto const YAN = gw1[
"YAN"];
6926 fund(env, gw, {gw1},
XRP(1'000), {USD(1'000)});
6927 fund(env, gw1, {gw},
XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
6928 AMM amm(env, gw1, YAN(10), USD(10));
6929 amm.deposit(gw, 1'000);
6932 BEAST_EXPECT(res && !res.value());
6934 BEAST_EXPECT(res && !res.value());
6941 testcase(
"test clawback from AMM account");
6942 using namespace jtx;
6945 Env env(*
this, features);
6948 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
6953 if (!features[featureAMMClawback])
6979 Issue usd(USD.issue().currency, amm.ammAccount());
6988 testcase(
"test AMMDeposit with frozen assets");
6989 using namespace jtx;
6996 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7006 Env env(*
this, features);
7007 testAMMDeposit(env, [&](
AMM& amm) {
7021 Env env(*
this, features);
7022 testAMMDeposit(env, [&](
AMM& amm) {
7033 if (features[featureAMMClawback])
7038 Env env(*
this, features);
7039 testAMMDeposit(env, [&](
AMM& amm) {
7054 Env env(*
this, features);
7055 testAMMDeposit(env, [&](
AMM& amm) {
7070 testcase(
"Fix Reserve Check On Withdrawal");
7071 using namespace jtx;
7076 auto test = [&](
auto&& cb) {
7077 Env env(*
this, features);
7078 auto const starting_xrp =
7079 reserve(env, 2) + env.
current()->fees().base * 5;
7080 env.
fund(starting_xrp, gw);
7081 env.
fund(starting_xrp, alice);
7082 env.
trust(USD(2'000), alice);
7084 env(pay(gw, alice, USD(2'000)));
7086 AMM amm(env, gw, EUR(1'000), USD(1'000));
7087 amm.deposit(alice, USD(1));
7092 test([&](
AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
7095 test([&](
AMM& amm) {
7098 .asset1Out = EUR(0.1),
7099 .asset2Out = USD(0.1),
7103 .asset1Out = USD(0.1),
7104 .asset2Out = EUR(0.1),
7109 test([&](
AMM& amm) {
7111 .
account = alice, .asset1Out = EUR(0.1), .err = err});
7120 testInvalidInstance();
7121 testInstanceCreate();
7122 testInvalidDeposit(all);
7123 testInvalidDeposit(all - featureAMMClawback);
7125 testInvalidWithdraw();
7127 testInvalidFeeVote();
7131 testBid(all - fixAMMv1_1);
7132 testInvalidAMMPayment();
7133 testBasicPaymentEngine(all);
7134 testBasicPaymentEngine(all - fixAMMv1_1);
7135 testBasicPaymentEngine(all - fixReducedOffersV2);
7136 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
7141 testAMMAndCLOB(all);
7142 testAMMAndCLOB(all - fixAMMv1_1);
7143 testTradingFee(all);
7144 testTradingFee(all - fixAMMv1_1);
7145 testAdjustedTokens(all);
7146 testAdjustedTokens(all - fixAMMv1_1);
7151 testSelection(all - fixAMMv1_1);
7152 testFixDefaultInnerObj();
7154 testFixOverflowOffer(all);
7155 testFixOverflowOffer(all - fixAMMv1_1);
7157 testFixChangeSpotPriceQuality(all);
7158 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
7159 testFixAMMOfferBlockedByLOB(all);
7160 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
7161 testLPTokenBalance(all);
7162 testLPTokenBalance(all - fixAMMv1_1);
7163 testAMMClawback(all);
7164 testAMMClawback(all - featureAMMClawback);
7165 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
7166 testAMMDepositWithFrozenAssets(all);
7167 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
7168 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
7169 testFixReserveCheckOnWithdrawal(all);
7170 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.