21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
23#include <test/jtx/CaptureLogs.h>
24#include <test/jtx/amount.h>
25#include <test/jtx/sendmax.h>
27#include <xrpld/app/misc/AMMHelpers.h>
28#include <xrpld/app/misc/AMMUtils.h>
29#include <xrpld/app/tx/detail/AMMBid.h>
31#include <xrpl/basics/Number.h>
32#include <xrpl/protocol/AMMCore.h>
33#include <xrpl/protocol/Feature.h>
35#include <boost/regex.hpp>
69 {{
USD(20'000),
BTC(0.5)}});
120 BEAST_EXPECT(amm.expectTradingFee(1'000));
121 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
148 BEAST_EXPECT(!ammAlice.ammExists());
157 BEAST_EXPECT(!ammAlice.ammExists());
165 BEAST_EXPECT(!ammAlice.ammExists());
182 BEAST_EXPECT(!ammAlice.ammExists());
191 BEAST_EXPECT(!ammAlice.ammExists());
200 BEAST_EXPECT(!ammAlice.ammExists());
219 BEAST_EXPECT(!ammAlice.ammExists());
244 BEAST_EXPECT(!ammAlice.ammExists());
309 auto const starting_xrp =
311 env.
fund(starting_xrp,
gw);
326 auto const starting_xrp =
328 env.
fund(starting_xrp,
gw);
383 auto const token1 = ammAlice.
lptIssue();
384 auto const token2 = ammAlice1.lptIssue();
409 auto const USD1 = gw1[
"USD"];
606 for (
auto const& it : invalidOptions)
625 jv[jss::TransactionType] = jss::AMMDeposit;
647 jv[jss::TransactionType] = jss::AMMDeposit;
652 jv[jss::LPTokenOut] =
855 [&](
AMM& ammAlice,
Env& env) {
857 if (!features[featureAMMClawback])
901 [&](
AMM& ammAlice,
Env& env) {
904 if (!features[featureAMMClawback])
963 [&](
AMM& ammAlice,
Env& env) {
1003 {{
USD(20'000),
BTC(0.5)}});
1007 Env env(*
this, features);
1022 if (features[featureAMMClawback])
1111 auto const starting_xrp =
1146 auto const starting_xrp =
1308 using namespace jtx;
1312 auto const baseFee = env.
current()->fees().base;
1326 for (
const Number deltaLPTokens :
1327 {
Number{UINT64_C(100000'0000000009), -10},
1328 Number{UINT64_C(100000'0000000001), -10}})
1334 deltaLPTokens.
mantissa(), deltaLPTokens.exponent()};
1347 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1348 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1352 const Number fr = deltaLPTokens / 1e7;
1356 const Number deltaXRP = fr * 1e10;
1357 const Number deltaUSD = fr * 1e4;
1367 XRP(10'000) + depositXRP,
1368 USD(10'000) + depositUSD,
1492 STAmount{USD, UINT64_C(10'000'000001), -6},
1608 using namespace jtx;
1611 [&](
AMM& ammAlice,
Env& env) {
1621 [&](
AMM& ammAlice,
Env& env) {
1647 .asset1Out =
USD(100),
1776 for (
auto const& it : invalidOptions)
1787 ter(std::get<5>(it)));
1938 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
2043 [&](
AMM& ammAlice,
Env&) {
2055 [&](
AMM& ammAlice,
Env&) {
2126 using namespace jtx;
2131 auto const baseFee = env.
current()->fees().base.drops();
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 auto const baseFee = env.current()->fees().base;
3300 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3301 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3305 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3306 env.app().config().features.erase(featureAMM);
3307 PreflightContext pfctx(
3310 env.current()->rules(),
3313 auto pf = AMMBid::preflight(pfctx);
3314 BEAST_EXPECT(pf == temDISABLED);
3315 env.app().config().features.insert(featureAMM);
3319 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3320 jtx.jv[
"TxnSignature"] =
"deadbeef";
3321 jtx.stx = env.ust(jtx);
3322 PreflightContext pfctx(
3325 env.current()->rules(),
3328 auto pf = AMMBid::preflight(pfctx);
3329 BEAST_EXPECT(pf != tesSUCCESS);
3333 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3334 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3335 jtx.jv[
"Asset2"].removeMember(
"issuer");
3336 jtx.stx = env.ust(jtx);
3337 PreflightContext pfctx(
3340 env.current()->rules(),
3343 auto pf = AMMBid::preflight(pfctx);
3344 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3352 testcase(
"Invalid AMM Payment");
3353 using namespace jtx;
3355 using namespace std::literals::chrono_literals;
3359 for (
auto const& acct : {gw, alice})
3363 fund(env, gw, {alice, carol},
XRP(1'000), {USD(100)});
3365 AMM ammAlice(env, acct,
XRP(10), USD(10));
3367 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3370 env(pay(carol, ammAlice.ammAccount(),
XRP(300)),
3373 env(pay(carol, ammAlice.ammAccount(), USD(10)),
3378 fund(env, gw, {alice, carol},
XRP(10'000'000), {USD(10'000)});
3380 AMM ammAlice(env, acct,
XRP(1'000'000), USD(100));
3382 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3385 env(pay(carol, ammAlice.ammAccount(),
XRP(1'000'000)),
3391 testAMM([&](
AMM& ammAlice,
Env& env) {
3392 auto const baseFee = env.
current()->fees().base;
3402 testAMM([&](
AMM& ammAlice,
Env& env) {
3403 auto const pk = carol.pk();
3404 auto const settleDelay = 100s;
3406 env.
current()->info().parentCloseTime + 200s;
3418 testAMM([&](
AMM& ammAlice,
Env& env) {
3425 [&](
AMM& ammAlice,
Env& env) {
3427 env(pay(alice, carol, USD(100)),
3431 env(pay(alice, carol,
XRP(100)),
3438 STAmount{USD, UINT64_C(99'999999999), -9}),
3444 STAmount{USD, UINT64_C(999'99999999), -8}),
3453 env(pay(alice, carol, USD(99.99)),
3462 {{
XRP(100), USD(100)}});
3465 testAMM([&](
AMM& ammAlice,
Env& env) {
3468 env(pay(alice, carol, USD(1)),
3473 env(pay(alice, carol,
XRP(1)),
3481 testAMM([&](
AMM& ammAlice,
Env& env) {
3487 env(pay(alice, carol, USD(1)),
3492 env(pay(alice, carol,
XRP(1)),
3500 testAMM([&](
AMM& ammAlice,
Env& env) {
3504 env(pay(alice, carol,
XRP(1)),
3515 testcase(
"Basic Payment");
3516 using namespace jtx;
3521 [&](
AMM& ammAlice,
Env& env) {
3522 env.
fund(jtx::XRP(30'000), bob);
3524 env(pay(bob, carol, USD(100)),
3530 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3532 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3535 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3537 {{
XRP(10'000), USD(10'100)}},
3544 [&](
AMM& ammAlice,
Env& env) {
3545 env.
fund(jtx::XRP(30'000), bob);
3547 env(pay(bob, carol, USD(100)),
sendmax(
XRP(100)));
3550 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3552 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3555 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3557 {{
XRP(10'000), USD(10'100)}},
3565 [&](
AMM& ammAlice,
Env& env) {
3566 env.
fund(jtx::XRP(30'000), bob);
3571 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3573 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3576 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3578 {{
XRP(10'000), USD(10'100)}},
3585 [&](
AMM& ammAlice,
Env& env) {
3586 env.
fund(jtx::XRP(30'000), bob);
3590 env(pay(bob, carol, USD(100)),
3597 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3599 BEAST_EXPECT(
expectLine(env, carol, USD(30'010)));
3603 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3607 env(pay(bob, carol, USD(100)),
3615 {{
XRP(10'000), USD(10'010)}},
3622 [&](
AMM& ammAlice,
Env& env) {
3625 env.
fund(jtx::XRP(30'000), bob);
3630 env(pay(bob, carol, USD(100)),
3637 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3642 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
3644 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3646 {{
XRP(10'000), USD(10'010)}},
3653 [&](
AMM& ammAlice,
Env& env) {
3654 env.
fund(jtx::XRP(30'000), bob);
3656 env(pay(bob, carol, USD(100)),
3662 {{
XRP(10'000), USD(10'000)}},
3672 Env env(*
this, features);
3674 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3678 auto ammEUR_XRP =
AMM(env, alice,
XRP(10'000), EUR(10'000));
3679 auto ammUSD_EUR =
AMM(env, alice, EUR(10'000), USD(10'000));
3682 env(pay(bob, carol, USD(100)),
3687 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3689 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3690 ammEUR_XRP.tokens()));
3691 if (!features[fixAMMv1_1])
3693 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3694 STAmount(USD, UINT64_C(9'970'097277662122), -12),
3695 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3696 ammUSD_EUR.tokens()));
3699 Amounts
const expectedAmounts =
3700 env.
closed()->rules().enabled(fixReducedOffersV2)
3701 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233787816), -14)}
3704 STAmount(USD, UINT64_C(29'90272233787818), -14)};
3706 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3710 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3711 STAmount(USD, UINT64_C(9'970'097277662172), -12),
3712 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3713 ammUSD_EUR.tokens()));
3716 Amounts
const expectedAmounts =
3717 env.
closed()->rules().enabled(fixReducedOffersV2)
3718 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233782839), -14)}
3721 STAmount(USD, UINT64_C(29'90272233782840), -14)};
3723 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3739 [&](
AMM& ammAlice,
Env& env) {
3742 env.
trust(EUR(2'000), alice);
3744 env(pay(gw, alice, EUR(1'000)));
3749 env(pay(bob, carol, USD(100)),
3756 STAmount(USD, UINT64_C(9'950'01249687578), -11),
3764 STAmount(EUR, UINT64_C(49'98750312422), -11)},
3766 STAmount(EUR, UINT64_C(49'98750312422), -11),
3767 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
3772 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3789 [&](
AMM& ammAlice,
Env& env) {
3790 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3794 env(pay(alice, carol, USD(200)),
3798 if (!features[fixAMMv1_1])
3801 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3803 BEAST_EXPECT(
expectLine(env, carol, USD(30'200)));
3809 STAmount(USD, UINT64_C(10'000'00000000001), -11),
3814 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
3822 ammCrtFee(env) -
txfee(env, 1)));
3825 {{
XRP(10'000), USD(10'100)}},
3834 Env env(*
this, features);
3835 fund(env, gw, {alice, bob, carol},
XRP(20'000), {USD(2'000)});
3839 AMM ammAlice(env, alice,
XRP(1'000), USD(1'050));
3840 env(pay(alice, carol, USD(200)),
3845 XRP(1'050), USD(1'000), ammAlice.
tokens()));
3846 BEAST_EXPECT(
expectLine(env, carol, USD(2'200)));
3852 [&](
AMM& ammAlice,
Env& env) {
3853 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
3855 env(offer(bob, USD(100),
XRP(100)));
3858 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3860 BEAST_EXPECT(
expectLine(env, bob, USD(1'100)));
3863 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3866 {{
XRP(10'000), USD(10'100)}},
3874 [&](
AMM& ammAlice,
Env& env) {
3875 env(
rate(gw, 1.25));
3881 env(offer(carol, EUR(100), GBP(100)));
3885 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
3887 BEAST_EXPECT(
expectLine(env, carol, GBP(29'875)));
3889 BEAST_EXPECT(
expectLine(env, carol, EUR(30'100)));
3892 {{GBP(1'000), EUR(1'100)}},
3898 [&](
AMM& amm,
Env& env) {
3899 env(
rate(gw, 1.001));
3901 env(offer(carol,
XRP(100), USD(55)));
3903 if (!features[fixAMMv1_1])
3913 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
3915 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
3926 BEAST_EXPECT(amm.expectBalances(
3928 STAmount{USD, UINT64_C(550'000000055), -9},
3937 STAmount{USD, 4'99999995, -8}}}}));
3941 STAmount(USD, UINT64_C(29'949'94999999494), -11));
3944 {{
XRP(1'000), USD(500)}},
3949 [&](
AMM& amm,
Env& env) {
3950 env(
rate(gw, 1.001));
3952 env(offer(carol,
XRP(10), USD(5.5)));
3954 if (!features[fixAMMv1_1])
3956 BEAST_EXPECT(amm.expectBalances(
3958 STAmount{USD, UINT64_C(505'050505050505), -12},
3964 BEAST_EXPECT(amm.expectBalances(
3966 STAmount{USD, UINT64_C(505'0505050505051), -13},
3971 {{
XRP(1'000), USD(500)}},
3977 [&](
AMM& ammAlice,
Env& env) {
3984 {GBP(2'000), EUR(2'000)},
3986 env(
rate(gw, 1.25));
3997 env(offer(carol, EUR(100), GBP(100)));
3999 if (!features[fixAMMv1_1])
4008 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
4009 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
4017 STAmount{EUR, UINT64_C(50'684828792831), -12},
4018 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
4030 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
4035 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
4047 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
4048 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
4056 STAmount{EUR, UINT64_C(27'06583722134028), -14},
4057 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
4069 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
4074 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
4077 BEAST_EXPECT(
expectLine(env, bob, GBP(2'010)));
4079 BEAST_EXPECT(
expectLine(env, ed, EUR(1'987.5)));
4081 {{GBP(1'000), EUR(1'100)}},
4094 [&](
AMM& ammAlice,
Env& env) {
4095 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
4096 env(
rate(gw, 1.25));
4098 env(pay(bob, carol, EUR(100)),
4104 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4106 BEAST_EXPECT(
expectLine(env, carol, EUR(30'080)));
4108 {{GBP(1'000), EUR(1'100)}},
4125 [&](
AMM& ammAlice,
Env& env) {
4128 auto const CAN = gw[
"CAN"];
4129 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
4130 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
4131 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
4132 env(trust(carol, USD(100)));
4133 env(
rate(gw, 1.25));
4135 env(offer(dan, CAN(200), GBP(200)));
4136 env(offer(ed, EUR(200), USD(200)));
4138 env(pay(bob, carol, USD(100)),
4139 path(~GBP, ~EUR, ~USD),
4144 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25), GBP(43.75)));
4146 GBP(10'125), EUR(10'000), ammAlice.
tokens()));
4147 BEAST_EXPECT(
expectLine(env, ed, EUR(300), USD(100)));
4148 BEAST_EXPECT(
expectLine(env, carol, USD(80)));
4150 {{GBP(10'000), EUR(10'125)}},
4157 [&](
AMM& ammAlice,
Env& env) {
4158 env(pay(alice, carol, USD(99.99)),
4163 env(pay(alice, carol, USD(100)),
4168 env(pay(alice, carol,
XRP(100)),
4179 {{
XRP(100), USD(100)}},
4186 Env env(*
this, features);
4187 auto const ETH = gw[
"ETH"];
4193 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4194 fund(env, gw, {carol, bob},
XRP(1'000), {USD(200)}, Fund::Acct);
4195 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4196 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4197 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4198 AMM xrp_usd(env, alice,
XRP(10'150), USD(10'200));
4199 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4200 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4201 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
4202 env(pay(bob, carol, USD(100)),
4203 path(~EUR, ~BTC, ~USD),
4205 path(~ETH, ~EUR, ~USD),
4207 if (!features[fixAMMv1_1])
4213 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
4216 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
4217 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4220 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4221 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4227 STAmount{USD, UINT64_C(10'126'06848287914), -11},
4234 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
4237 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
4238 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4241 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4242 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4248 STAmount{USD, UINT64_C(10'126'06848287943), -11},
4258 BEAST_EXPECT(xrp_eur.expectBalances(
4259 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
4261 EUR(10'000), BTC(10'200), eur_btc.
tokens()));
4263 BTC(10'100), USD(10'000), btc_usd.
tokens()));
4265 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4270 Env env(*
this, features);
4271 auto const ETH = gw[
"ETH"];
4277 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4278 fund(env, gw, {carol, bob},
XRP(1000), {USD(200)}, Fund::Acct);
4279 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4280 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4281 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4282 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4283 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4284 env(pay(bob, carol, USD(100)),
4285 path(~EUR, ~BTC, ~USD),
4286 path(~ETH, ~EUR, ~BTC, ~USD),
4288 if (!features[fixAMMv1_1])
4292 BEAST_EXPECT(xrp_eur.expectBalances(
4294 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
4297 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4298 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4301 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
4306 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
4309 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
4310 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4315 BEAST_EXPECT(xrp_eur.expectBalances(
4317 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
4320 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4321 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4324 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
4329 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
4332 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
4333 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4336 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4342 [&](
AMM& ammAlice,
Env& env) {
4344 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4345 env(trust(alice, EUR(200)));
4346 for (
int i = 0; i < 30; ++i)
4347 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4350 env(offer(alice, EUR(140),
XRP(100)));
4351 env(pay(bob, carol, USD(100)),
4355 if (!features[fixAMMv1_1])
4360 STAmount{USD, UINT64_C(9'970'089730807577), -12},
4365 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
4371 STAmount{USD, UINT64_C(9'970'089730807827), -12},
4376 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
4387 [&](
AMM& ammAlice,
Env& env) {
4389 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4390 env(trust(alice, EUR(200)));
4391 for (
int i = 0; i < 29; ++i)
4392 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4395 env(offer(alice, EUR(140),
XRP(100)));
4396 env(pay(bob, carol, USD(100)),
4402 if (!features[fixAMMv1_1])
4408 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4412 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4418 {{{
STAmount{EUR, UINT64_C(39'1858572), -7},
4429 Env env(*
this, features);
4430 fund(env, gw, {alice, carol, bob},
XRP(30'000), {USD(30'000)});
4431 env(offer(bob,
XRP(100), USD(100.001)));
4432 AMM ammAlice(env, alice,
XRP(10'000), USD(10'100));
4433 env(offer(carol, USD(100),
XRP(100)));
4434 if (!features[fixAMMv1_1])
4438 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4445 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
4451 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4458 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
4459 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4465 [&](
AMM& ammAlice,
Env& env) {
4469 env(pay(alice, carol, USD(1)),
4484 testcase(
"AMM Tokens");
4485 using namespace jtx;
4488 testAMM([&](
AMM& ammAlice,
Env& env) {
4489 auto const baseFee = env.
current()->fees().base.drops();
4490 auto const token1 = ammAlice.
lptIssue();
4497 env(offer(carol,
STAmount{token1, 5'000'000}, priceXRP));
4499 env(offer(alice, priceXRP,
STAmount{token1, 5'000'000}));
4507 ammAlice.
vote(carol, 1'000);
4509 ammAlice.
vote(carol, 0);
4512 env(ammAlice.
bid({.account = carol, .bidMin = 100}));
4537 testAMM([&](
AMM& ammAlice,
Env& env) {
4538 ammAlice.
deposit(carol, 1'000'000);
4539 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
4540 AMM ammAlice1(env, alice,
XRP(10'000), EUR(10'000));
4541 ammAlice1.deposit(carol, 1'000'000);
4542 auto const token1 = ammAlice.
lptIssue();
4543 auto const token2 = ammAlice1.lptIssue();
4563 testAMM([&](
AMM& ammAlice,
Env& env) {
4564 auto const token1 = ammAlice.
lptIssue();
4567 ammAlice.
deposit(carol, 1'000'000);
4573 env(pay(alice, carol,
STAmount{token1, 100}));
4583 env(pay(carol, alice,
STAmount{token1, 100}));
4595 testcase(
"Amendment");
4596 using namespace jtx;
4601 all - featureAMM - fixUniversalNumber};
4603 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
4605 Env env{*
this, feature};
4606 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
4624 using namespace jtx;
4626 testAMM([&](
AMM& ammAlice,
Env& env) {
4627 auto const info = env.
rpc(
4631 "{\"account\": \"" + to_string(ammAlice.
ammAccount()) +
4634 info[jss::result][jss::account_data][jss::Flags].asUInt();
4644 testcase(
"Rippling");
4645 using namespace jtx;
4661 auto const TSTA = A[
"TST"];
4662 auto const TSTB = B[
"TST"];
4671 env.
trust(TSTA(10'000), C);
4672 env.
trust(TSTB(10'000), C);
4673 env(pay(A, C, TSTA(10'000)));
4674 env(pay(B, C, TSTB(10'000)));
4675 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
4676 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
4683 env(pay(C, D,
STAmount{ammIss, 10}),
4685 path(amm.ammAccount()),
4694 testcase(
"AMMAndCLOB, offer quality change");
4695 using namespace jtx;
4696 auto const gw =
Account(
"gw");
4697 auto const TST = gw[
"TST"];
4698 auto const LP1 =
Account(
"LP1");
4699 auto const LP2 =
Account(
"LP2");
4701 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4702 Env env(*
this, features);
4703 env.
fund(
XRP(30'000'000'000), gw);
4704 env(offer(gw,
XRP(11'500'000'000), TST(1'000'000'000)));
4708 env(offer(LP1, TST(25),
XRPAmount(287'500'000)));
4713 env(offer(LP2, TST(25),
XRPAmount(287'500'000)));
4725 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
4731 lp2TakerGets = offer[
"taker_gets"].asString();
4732 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
4737 if (!features[fixAMMv1_1])
4741 STAmount{TST, UINT64_C(1'68737984885388), -14}),
4747 STAmount{TST, UINT64_C(1'68737976189735), -14}),
4756 BEAST_EXPECT(lp2TakerGets == offer[
"taker_gets"].asString());
4758 lp2TakerPays == offer[
"taker_pays"][
"value"].asString());
4765 testcase(
"Trading Fee");
4766 using namespace jtx;
4770 [&](
AMM& ammAlice,
Env& env) {
4772 ammAlice.
deposit(carol, USD(3'000));
4776 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4778 ammAlice.
vote(alice, 1'000);
4782 ammAlice.
deposit(carol, USD(3'000));
4784 carol,
IOUAmount{994'981155689671, -12}));
4785 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4787 ammAlice.
vote(alice, 0);
4793 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
4795 {{USD(1'000), EUR(1'000)}},
4803 [&](
AMM& ammAlice,
Env& env) {
4805 auto tokensFee = ammAlice.
deposit(
4806 carol, USD(1'000), std::nullopt,
STAmount{USD, 1, -1});
4809 ammAlice.
vote(alice, 0);
4811 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4814 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4815 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4825 [&](
AMM& ammAlice,
Env& env) {
4827 auto const tokensFee = ammAlice.
deposit(
4828 carol, USD(200), std::nullopt,
STAmount{USD, 2020, -6});
4831 ammAlice.
vote(alice, 0);
4833 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4836 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4837 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4846 [&](
AMM& ammAlice,
Env& env) {
4848 ammAlice.
deposit(carol, USD(3'000));
4851 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4853 ammAlice.
vote(alice, 1'000);
4860 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
4862 {{USD(1'000), EUR(1'000)}},
4869 [&](
AMM& ammAlice,
Env& env) {
4870 ammAlice.
deposit(carol, 1'000'000);
4871 auto const tokensFee = ammAlice.
withdraw(
4872 carol, USD(100), std::nullopt,
IOUAmount{520, 0});
4874 auto const balanceAfterWithdraw = [&]() {
4875 if (!features[fixAMMv1_1])
4876 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
4877 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
4879 BEAST_EXPECT(env.
balance(carol, USD) == balanceAfterWithdraw);
4881 auto const deposit = balanceAfterWithdraw - USD(29'000);
4882 ammAlice.
deposit(carol, deposit);
4884 ammAlice.
vote(alice, 0);
4886 auto const tokensNoFee = ammAlice.
withdraw(carol, deposit);
4887 if (!features[fixAMMv1_1])
4890 STAmount(USD, UINT64_C(30'443'43891402717), -11));
4894 STAmount(USD, UINT64_C(30'443'43891402716), -11));
4897 if (!features[fixAMMv1_1])
4899 tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4902 tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4903 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4912 [&](
AMM& ammAlice,
Env& env) {
4918 {USD(1'000), EUR(1'000)},
4921 BEAST_EXPECT(
expectLine(env, alice, EUR(28'990)));
4922 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4923 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4925 env(pay(carol, alice, EUR(10)),
4931 BEAST_EXPECT(
expectLine(env, alice, EUR(29'000)));
4932 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4933 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4936 ammAlice.
vote(alice, 1'000);
4939 env(pay(bob, carol, USD(10)),
4946 env, bob,
STAmount{EUR, UINT64_C(989'8989898989899), -13}));
4948 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4951 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
4954 {{USD(1'000), EUR(1'010)}},
4961 [&](
AMM& ammAlice,
Env& env) {
4963 env(offer(carol, EUR(10), USD(10)));
4965 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4966 BEAST_EXPECT(
expectLine(env, carol, EUR(30'010)));
4968 env(offer(carol, USD(10), EUR(10)));
4971 ammAlice.
vote(alice, 500);
4973 env(offer(carol, EUR(10), USD(10)));
4980 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
4984 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
4990 STAmount{EUR, UINT64_C(5'025125628140703), -15},
4991 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
4992 if (!features[fixAMMv1_1])
4995 STAmount{USD, UINT64_C(1'004'974874371859), -12},
4996 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5002 STAmount{USD, UINT64_C(1'004'97487437186), -11},
5003 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5007 {{USD(1'000), EUR(1'010)}},
5017 Env env(*
this, features);
5022 {alice, bob, carol, ed},
5024 {USD(2'000), EUR(2'000)});
5025 env(offer(carol, EUR(5), USD(5)));
5026 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
5027 env(pay(bob, ed, USD(10)),
5031 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5032 if (!features[fixAMMv1_1])
5034 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5036 USD(1'000), EUR(1'005), ammAlice.
tokens()));
5041 env, bob,
STAmount(EUR, UINT64_C(1989'999999999999), -12)));
5044 STAmount(EUR, UINT64_C(1005'000000000001), -12),
5053 Env env(*
this, features);
5058 {alice, bob, carol, ed},
5060 {USD(2'000), EUR(2'000)});
5061 env(offer(carol, EUR(5), USD(5)));
5063 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 250);
5064 env(pay(bob, ed, USD(10)),
5068 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5069 if (!features[fixAMMv1_1])
5074 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
5077 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
5085 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
5088 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
5098 Env env(*
this, features);
5103 {alice, bob, carol, ed},
5105 {USD(2'000), EUR(2'000)});
5106 env(offer(carol, EUR(10), USD(10)));
5108 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5109 env(pay(bob, ed, USD(10)),
5113 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5114 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5116 USD(1'005), EUR(1'000), ammAlice.
tokens()));
5125 Env env(*
this, features);
5130 {alice, bob, carol, ed},
5132 {USD(2'000), EUR(2'000)});
5133 env(offer(carol, EUR(9), USD(9)));
5135 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5136 env(pay(bob, ed, USD(10)),
5140 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5142 env, bob,
STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
5145 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
5154 testcase(
"Adjusted Deposit/Withdraw Tokens");
5156 using namespace jtx;
5160 [&](
AMM& ammAlice,
Env& env) {
5168 Account const nataly(
"nataly");
5172 {bob, ed, paul, dan, chris, simon, ben, nataly},
5175 for (
int i = 0; i < 10; ++i)
5179 ammAlice.
deposit(simon, USD(0.1));
5181 ammAlice.
deposit(chris, USD(1));
5183 ammAlice.
deposit(dan, USD(10));
5185 ammAlice.
deposit(bob, USD(100));
5187 ammAlice.
deposit(carol, USD(1'000));
5189 ammAlice.
deposit(ed, USD(10'000));
5191 ammAlice.
deposit(paul, USD(100'000));
5193 ammAlice.
deposit(nataly, USD(1'000'000));
5199 if (!features[fixAMMv1_1])
5202 STAmount{USD, UINT64_C(10'000'0000000013), -10},
5207 BEAST_EXPECT(
expectLine(env, ben, USD(1'500'000)));
5208 BEAST_EXPECT(
expectLine(env, simon, USD(1'500'000)));
5209 BEAST_EXPECT(
expectLine(env, chris, USD(1'500'000)));
5210 BEAST_EXPECT(
expectLine(env, dan, USD(1'500'000)));
5211 if (!features[fixAMMv1_1])
5215 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
5217 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5218 BEAST_EXPECT(
expectLine(env, ed, USD(1'500'000)));
5219 BEAST_EXPECT(
expectLine(env, paul, USD(1'500'000)));
5220 if (!features[fixAMMv1_1])
5224 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
5229 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
5232 if (!features[fixAMMv1_1])
5236 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
5238 BEAST_EXPECT(
expectLine(env, alice, USD(30'000)));
5244 29950000000 - env.
current()->fees().base.drops()));
5252 testAMM([&](
AMM& ammAlice,
Env& env) {
5260 Account const nataly(
"nataly");
5264 {bob, ed, paul, dan, chris, simon, ben, nataly},
5268 for (
int i = 0; i < 10; ++i)
5295 auto const xrpBalance = (
XRP(2'000'000) -
txfee(env, 20)).getText();
5301 auto const baseFee = env.
current()->fees().base.drops();
5319 testcase(
"Auto Delete");
5321 using namespace jtx;
5332 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5333 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5338 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5343 amm.withdrawAll(gw);
5344 BEAST_EXPECT(amm.ammExists());
5369 env(trust(alice,
STAmount{amm.lptIssue(), 10'000}),
5384 amm.expectBalances(
XRP(10'000), USD(10'000), amm.tokens()));
5385 BEAST_EXPECT(amm.expectTradingFee(1'000));
5386 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5390 amm.withdrawAll(alice);
5391 BEAST_EXPECT(!amm.ammExists());
5392 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5403 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5404 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5409 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5413 amm.withdrawAll(gw);
5414 BEAST_EXPECT(amm.ammExists());
5418 BEAST_EXPECT(amm.ammExists());
5420 amm.ammDelete(alice);
5421 BEAST_EXPECT(!amm.ammExists());
5422 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5432 testcase(
"Clawback");
5433 using namespace jtx;
5437 AMM amm(env, gw,
XRP(1'000), USD(1'000));
5445 using namespace jtx;
5446 testAMM([&](
AMM& amm,
Env& env) {
5447 amm.setClose(
false);
5448 auto const info = env.
rpc(
5452 "{\"account\": \"" + to_string(amm.ammAccount()) +
"\"}"));
5456 info[jss::result][jss::account_data][jss::AMMID]
5457 .asString() == to_string(amm.ammID()));
5463 amm.deposit(carol, 1'000);
5464 auto affected = env.
meta()->getJson(
5465 JsonOptions::none)[sfAffectedNodes.fieldName];
5469 for (
auto const& node : affected)
5471 if (node.isMember(sfModifiedNode.fieldName) &&
5472 node[sfModifiedNode.fieldName]
5473 [sfLedgerEntryType.fieldName]
5474 .asString() ==
"AccountRoot" &&
5475 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
5477 .asString() == to_string(amm.ammAccount()))
5479 found = node[sfModifiedNode.fieldName]
5480 [sfFinalFields.fieldName][jss::AMMID]
5481 .asString() == to_string(amm.ammID());
5485 BEAST_EXPECT(found);
5497 testcase(
"Offer/Strand Selection");
5498 using namespace jtx;
5501 auto const ETH = gw1[
"ETH"];
5502 auto const CAN = gw1[
"CAN"];
5508 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5509 fund(env, gw, {alice, carol, bob, ed},
XRP(2'000), {USD(2'000)});
5514 {alice, carol, bob, ed},
5515 {ETH(2'000), CAN(2'000)},
5517 env(
rate(gw, gwRate));
5518 env(
rate(gw1, gw1Rate));
5522 for (
auto const& rates :
5537 for (
auto i = 0; i < 3; ++i)
5539 Env env(*
this, features);
5540 prep(env, rates.first, rates.second);
5542 if (i == 0 || i == 2)
5548 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5549 env(pay(carol, bob, USD(100)),
5556 BEAST_EXPECT(amm->expectBalances(
5557 USD(1'000), ETH(1'000), amm->tokens()));
5559 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5560 q[i] = Quality(Amounts{
5561 ETH(2'000) - env.
balance(carol, ETH),
5562 env.
balance(bob, USD) - USD(2'000)});
5565 BEAST_EXPECT(q[0] > q[1]);
5567 BEAST_EXPECT(q[0] == q[2]);
5574 for (
auto i = 0; i < 3; ++i)
5576 Env env(*
this, features);
5577 prep(env, rates.first, rates.second);
5579 if (i == 0 || i == 2)
5585 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5586 env(offer(alice, USD(400), ETH(400)));
5591 BEAST_EXPECT(amm->expectBalances(
5592 USD(1'000), ETH(1'000), amm->tokens()));
5594 if (i == 0 || i == 2)
5603 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
5614 for (
auto i = 0; i < 3; ++i)
5616 Env env(*
this, features);
5617 prep(env, rates.first, rates.second);
5619 if (i == 0 || i == 2)
5625 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5626 env(pay(carol, bob, USD(100)),
5633 BEAST_EXPECT(!amm->expectBalances(
5634 USD(1'000), ETH(1'000), amm->tokens()));
5636 if (i == 2 && !features[fixAMMv1_1])
5638 if (rates.first == 1.5)
5640 if (!features[fixAMMv1_1])
5648 UINT64_C(378'6327949540823),
5652 UINT64_C(283'9745962155617),
5662 UINT64_C(378'6327949540813),
5666 UINT64_C(283'974596215561),
5671 if (!features[fixAMMv1_1])
5679 UINT64_C(325'299461620749),
5683 UINT64_C(243'9745962155617),
5693 UINT64_C(325'299461620748),
5697 UINT64_C(243'974596215561),
5703 if (rates.first == 1.5)
5711 ETH, UINT64_C(378'6327949540812), -13},
5714 UINT64_C(283'9745962155609),
5725 ETH, UINT64_C(325'2994616207479), -13},
5728 UINT64_C(243'9745962155609),
5732 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5733 q[i] = Quality(Amounts{
5734 ETH(2'000) - env.
balance(carol, ETH),
5735 env.
balance(bob, USD) - USD(2'000)});
5738 BEAST_EXPECT(q[1] > q[0]);
5740 BEAST_EXPECT(q[2] > q[1]);
5744 for (
auto i = 0; i < 3; ++i)
5746 Env env(*
this, features);
5747 prep(env, rates.first, rates.second);
5749 if (i == 0 || i == 2)
5755 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5756 env(offer(alice, USD(250), ETH(400)));
5761 BEAST_EXPECT(!amm->expectBalances(
5762 USD(1'000), ETH(1'000), amm->tokens()));
5768 if (rates.first == 1.5)
5770 if (!features[fixAMMv1_1])
5773 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
5780 USD, UINT64_C(40'5694150420947), -13},
5782 ETH, UINT64_C(64'91106406735152), -14},
5796 ETH, UINT64_C(335'0889359326475), -13},
5798 USD, UINT64_C(209'4305849579047), -13},
5805 if (!features[fixAMMv1_1])
5814 ETH, UINT64_C(335'0889359326485), -13},
5816 USD, UINT64_C(209'4305849579053), -13},
5829 ETH, UINT64_C(335'0889359326475), -13},
5831 USD, UINT64_C(209'4305849579047), -13},
5857 for (
auto i = 0; i < 3; ++i)
5859 Env env(*
this, features);
5860 prep(env, rates.first, rates.second);
5863 if (i == 0 || i == 2)
5871 amm.emplace(env, ed, ETH(1'000), USD(1'000));
5873 env(pay(carol, bob, USD(100)),
5879 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5881 if (i == 2 && !features[fixAMMv1_1])
5883 if (rates.first == 1.5)
5886 BEAST_EXPECT(amm->expectBalances(
5887 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
5893 BEAST_EXPECT(amm->expectBalances(
5895 ETH, UINT64_C(1'179'540094339627), -12},
5896 STAmount{USD, UINT64_C(847'7880529867501), -13},
5905 UINT64_C(343'3179205198749),
5909 UINT64_C(343'3179205198749),
5915 UINT64_C(362'2119470132499),
5919 UINT64_C(362'2119470132499),
5926 if (rates.first == 1.5)
5929 BEAST_EXPECT(amm->expectBalances(
5931 ETH, UINT64_C(1'176'660389557593), -12},
5937 BEAST_EXPECT(amm->expectBalances(
5938 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
5939 STAmount{USD, UINT64_C(847'7880529867501), -13},
5948 UINT64_C(343'3179205198749),
5952 UINT64_C(343'3179205198749),
5958 UINT64_C(362'2119470132499),
5962 UINT64_C(362'2119470132499),
5967 q[i] = Quality(Amounts{
5968 ETH(2'000) - env.
balance(carol, ETH),
5969 env.
balance(bob, USD) - USD(2'000)});
5971 BEAST_EXPECT(q[1] > q[0]);
5972 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5980 testcase(
"Fix Default Inner Object");
5981 using namespace jtx;
5992 Env env(*
this, features);
5993 fund(env, gw, {alice},
XRP(1'000), {USD(10)});
5999 {.tfee = tfee, .close = closeLedger});
6000 amm.deposit(alice, USD(10),
XRP(10));
6003 .
account = gw, .asset1Out = USD(1), .err =
ter(err2)});
6011 .
account = gw, .asset1Out = USD(2), .err =
ter(err4)});
6018 all - fixInnerObjTemplate,
6033 all - fixInnerObjTemplate,
6045 all - fixInnerObjTemplate,
6054 all - fixInnerObjTemplate,
6068 all - fixInnerObjTemplate,
6080 testcase(
"Fix changeSpotPriceQuality");
6081 using namespace jtx;
6086 SucceedShouldSucceedResize,
6098 auto const xrpIouAmounts10_100 =
6100 auto const iouXrpAmounts10_100 =
6105 {
"0.001519763260828713",
"1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
6106 {
"0.01099814367603737",
"1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
6107 {
"0.78",
"796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
6108 {
"105439.2955578965",
"49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
6109 {
"12408293.23445213",
"4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
6110 {
"1892611",
"0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
6111 {
"423028.8508101858",
"3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
6112 {
"44565388.41001027",
"73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
6113 {
"66831.68494832662",
"16", Quality{6346111134641742975}, 0, FailShouldSucceed},
6114 {
"675.9287302203422",
"1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
6115 {
"7047.112186735699",
"1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
6116 {
"840236.4402981238",
"47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
6117 {
"992715.618909774",
"189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
6118 {
"504636667521",
"185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
6119 {
"992706.7218636649",
"189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
6120 {
"1.068737911388205",
"127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
6121 {
"17932506.56880419",
"189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
6122 {
"1.066379294658174",
"128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
6123 {
"350131413924",
"1576879.110907892", Quality{6487411636539049449}, 650,
Fail},
6124 {
"422093460",
"2.731797662057464", Quality{6702911108534394924}, 1000,
Fail},
6125 {
"76128132223",
"367172.7148422662", Quality{6487263463413514240}, 548,
Fail},
6126 {
"132701839250",
"280703770.7695443", Quality{6273750681188885075}, 562,
Fail},
6127 {
"994165.7604612011",
"189551302411", Quality{5697835592690668727}, 815,
Fail},
6128 {
"45053.33303227917",
"86612695359", Quality{5625695218943638190}, 500,
Fail},
6129 {
"199649.077043865",
"14017933007", Quality{5766034667318524880}, 324,
Fail},
6130 {
"27751824831.70903",
"78896950", Quality{6272538159621630432}, 500,
Fail},
6131 {
"225.3731275781907",
"156431793648", Quality{5477818047604078924}, 989,
Fail},
6132 {
"199649.077043865",
"14017933007", Quality{5766036094462806309}, 324,
Fail},
6133 {
"3.590272027140361",
"20677643641", Quality{5406056147042156356}, 808,
Fail},
6134 {
"1.070884664490231",
"127604712776", Quality{5268620608623825741}, 293,
Fail},
6135 {
"3272.448829820197",
"6275124076", Quality{5625710328924117902}, 81,
Fail},
6136 {
"0.009059512633902926",
"7994028", Quality{5477511954775533172}, 1000,
Fail},
6137 {
"1",
"1.0", Quality{0}, 100,
Fail},
6138 {
"1.0",
"1", Quality{0}, 100,
Fail},
6139 {
"10",
"10.0", Quality{xrpIouAmounts10_100}, 100,
Fail},
6140 {
"10.0",
"10", Quality{iouXrpAmounts10_100}, 100,
Fail},
6141 {
"69864389131",
"287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
6142 {
"4328342973",
"12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
6143 {
"32347017",
"7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
6144 {
"61697206161",
"36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
6145 {
"1654524979",
"7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
6146 {
"88621.22277293179",
"5128418948", Quality{5766347291552869205}, 380, Succeed},
6147 {
"1892611",
"0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
6148 {
"4542.639373338766",
"24554809", Quality{5838994982188783710}, 0, Succeed},
6149 {
"5132932546",
"88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
6150 {
"78929964.1549083",
"1506494795", Quality{5986890029845558688}, 589, Succeed},
6151 {
"10096561906",
"44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
6152 {
"5092.219565514988",
"8768257694", Quality{5626349534958379008}, 503, Succeed},
6153 {
"1819778294",
"8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
6154 {
"6970462.633911943",
"57359281", Quality{6054087899185946624}, 850, Succeed},
6155 {
"3983448845",
"2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
6159 {
"771493171",
"1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
6163 boost::regex rx(
"^\\d+$");
6164 boost::smatch match;
6167 Env env(*
this, features, std::make_unique<CaptureLogs>(&logs));
6168 auto rules = env.
current()->rules();
6170 for (
auto const& t : tests)
6177 auto const& quality = std::get<Quality>(t);
6178 auto const tfee = std::get<std::uint16_t>(t);
6179 auto const status = std::get<Status>(t);
6180 auto const poolInIsXRP =
6181 boost::regex_search(std::get<0>(t), match, rx);
6182 auto const poolOutIsXRP =
6183 boost::regex_search(std::get<1>(t), match, rx);
6184 assert(!(poolInIsXRP && poolOutIsXRP));
6185 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
6186 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
6190 Amounts{poolIn, poolOut},
6197 if (status == SucceedShouldSucceedResize)
6199 if (!features[fixAMMv1_1])
6200 BEAST_EXPECT(Quality{*amounts} < quality);
6202 BEAST_EXPECT(Quality{*amounts} >= quality);
6204 else if (status == Succeed)
6206 if (!features[fixAMMv1_1])
6208 Quality{*amounts} >= quality ||
6210 Quality{*amounts}, quality,
Number{1, -7}));
6212 BEAST_EXPECT(Quality{*amounts} >= quality);
6214 else if (status == FailShouldSucceed)
6217 features[fixAMMv1_1] &&
6218 Quality{*amounts} >= quality);
6220 else if (status == SucceedShouldFail)
6223 !features[fixAMMv1_1] &&
6224 Quality{*amounts} < quality &&
6226 Quality{*amounts}, quality,
Number{1, -7}));
6235 if (status ==
Fail && quality != Quality{0})
6237 auto tinyOffer = [&]() {
6244 Amounts{poolIn, poolOut},
6248 else if (
isXRP(poolOut))
6253 Amounts{poolIn, poolOut},
6258 auto const takerPays = toAmount<STAmount>(
6263 Amounts{poolIn, poolOut}, takerPays, tfee)};
6265 BEAST_EXPECT(Quality(tinyOffer) < quality);
6267 else if (status == FailShouldSucceed)
6269 BEAST_EXPECT(!features[fixAMMv1_1]);
6271 else if (status == SucceedShouldFail)
6273 BEAST_EXPECT(features[fixAMMv1_1]);
6280 !strcmp(e.
what(),
"changeSpotPriceQuality failed"));
6282 !features[fixAMMv1_1] && status == FailShouldSucceed);
6291 BEAST_EXPECT(!res.has_value());
6298 using namespace jtx;
6300 testAMM([&](
AMM& ammAlice,
Env& env) {
6308 testAMM([&](
AMM& ammAlice,
Env& env) {
6316 testAMM([&](
AMM& ammAlice,
Env& env) {
6324 testAMM([&](
AMM& ammAlice,
Env& env) {
6327 .asset2Out =
XRP(100),
6333 testAMM([&](
AMM& ammAlice,
Env& env) {
6336 .asset2Out = BAD(100),
6342 testAMM([&](
AMM& ammAlice,
Env& env) {
6344 jv[jss::TransactionType] = jss::AMMWithdraw;
6346 jv[jss::Account] = alice.human();
6348 XRP(100).value().setJson(jv[jss::Amount]);
6349 USD(100).value().setJson(jv[jss::EPrice]);
6357 using namespace jtx;
6363 Account const gatehub{
"gatehub"};
6364 Account const bitstamp{
"bitstamp"};
6365 Account const trader{
"trader"};
6366 auto const usdGH = gatehub[
"USD"];
6367 auto const btcGH = gatehub[
"BTC"];
6368 auto const usdBIT = bitstamp[
"USD"];
6372 char const* testCase;
6373 double const poolUsdBIT;
6374 double const poolUsdGH;
6386 double const offer1BtcGH = 0.1;
6387 double const offer2BtcGH = 0.1;
6388 double const offer2UsdGH = 1;
6389 double const rateBIT = 0.0;
6390 double const rateGH = 0.0;
6395 for (
auto const& input : {
6397 .testCase =
"Test Fix Overflow Offer",
6400 .sendMaxUsdBIT{usdBIT(50)},
6401 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6404 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6405 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6406 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6407 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6408 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6409 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6410 .lpTokenBalance = {28'61817604250837, -14},
6418 .testCase =
"Overflow test {1, 100, 0.111}",
6421 .sendMaxUsdBIT{usdBIT(0.111)},
6422 .sendUsdGH{usdGH, 100},
6425 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6426 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6427 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6428 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6429 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6430 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6431 .lpTokenBalance{10, 0},
6432 .offer1BtcGH = 1e-5,
6434 .offer2UsdGH = 1e-5,
6439 .testCase =
"Overflow test {1, 100, 1.00}",
6442 .sendMaxUsdBIT{usdBIT(1.00)},
6443 .sendUsdGH{usdGH, 100},
6446 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6447 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6448 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6449 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6450 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6451 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6452 .lpTokenBalance{10, 0},
6453 .offer1BtcGH = 1e-5,
6455 .offer2UsdGH = 1e-5,
6460 .testCase =
"Overflow test {1, 100, 4.6432}",
6463 .sendMaxUsdBIT{usdBIT(4.6432)},
6464 .sendUsdGH{usdGH, 100},
6467 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6468 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6469 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6470 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6471 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6472 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6473 .lpTokenBalance{10, 0},
6474 .offer1BtcGH = 1e-5,
6476 .offer2UsdGH = 1e-5,
6481 .testCase =
"Overflow test {1, 100, 10}",
6484 .sendMaxUsdBIT{usdBIT(10)},
6485 .sendUsdGH{usdGH, 100},
6488 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6489 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6490 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6491 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6492 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6493 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6494 .lpTokenBalance{10, 0},
6495 .offer1BtcGH = 1e-5,
6497 .offer2UsdGH = 1e-5,
6502 .testCase =
"Overflow test {50, 100, 5.55}",
6505 .sendMaxUsdBIT{usdBIT(5.55)},
6506 .sendUsdGH{usdGH, 100},
6509 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6510 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6511 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6512 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6513 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6514 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6515 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6516 .offer1BtcGH = 1e-5,
6518 .offer2UsdGH = 1e-5,
6523 .testCase =
"Overflow test {50, 100, 50.00}",
6526 .sendMaxUsdBIT{usdBIT(50.00)},
6527 .sendUsdGH{usdGH, 100},
6528 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6529 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6530 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6531 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6532 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6533 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6534 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6535 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6536 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6537 .offer1BtcGH = 1e-5,
6539 .offer2UsdGH = 1e-5,
6544 .testCase =
"Overflow test {50, 100, 232.16}",
6547 .sendMaxUsdBIT{usdBIT(232.16)},
6548 .sendUsdGH{usdGH, 100},
6551 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6552 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6553 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6554 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6555 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6556 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6557 .lpTokenBalance{70'71067811865475, -14},
6558 .offer1BtcGH = 1e-5,
6560 .offer2UsdGH = 1e-5,
6565 .testCase =
"Overflow test {50, 100, 500}",
6568 .sendMaxUsdBIT{usdBIT(500)},
6569 .sendUsdGH{usdGH, 100},
6572 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6573 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6574 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6575 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6576 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6577 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6578 .lpTokenBalance{70'71067811865475, -14},
6579 .offer1BtcGH = 1e-5,
6581 .offer2UsdGH = 1e-5,
6587 testcase(input.testCase);
6588 for (
auto const& features :
6589 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
6591 Env env(*
this, features, std::make_unique<CaptureLogs>(&logs));
6593 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6596 if (input.rateGH != 0.0)
6597 env(
rate(gatehub, input.rateGH));
6598 if (input.rateBIT != 0.0)
6599 env(
rate(bitstamp, input.rateBIT));
6601 env(trust(trader, usdGH(10'000'000)));
6602 env(trust(trader, usdBIT(10'000'000)));
6603 env(trust(trader, btcGH(10'000'000)));
6606 env(pay(gatehub, trader, usdGH(100'000)));
6607 env(pay(gatehub, trader, btcGH(100'000)));
6608 env(pay(bitstamp, trader, usdBIT(100'000)));
6614 usdGH(input.poolUsdGH),
6615 usdBIT(input.poolUsdBIT)};
6619 amm.getLPTokensBalance();
6621 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6624 btcGH(input.offer2BtcGH),
6625 usdGH(input.offer2UsdGH)));
6628 env(pay(trader, trader, input.sendUsdGH),
6630 path(~btcGH, ~usdGH),
6635 auto const failUsdGH =
6636 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6637 auto const failUsdBIT =
6638 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6639 auto const goodUsdGH =
6640 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6641 auto const goodUsdBIT =
6642 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6643 if (!features[fixAMMOverflowOffer])
6645 BEAST_EXPECT(amm.expectBalances(
6646 failUsdGH, failUsdBIT, input.lpTokenBalance));
6650 BEAST_EXPECT(amm.expectBalances(
6651 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
6656 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6660 Number const sqrtPoolProduct =
6661 root2(goodUsdGH * goodUsdBIT);
6672 (sqrtPoolProduct +
Number{1, -14} >=
6673 input.lpTokenBalance));
6682 testcase(
"swapRounding");
6683 using namespace jtx;
6685 const STAmount xrpPool{
XRP, UINT64_C(51600'000981)};
6686 const STAmount iouPool{USD, UINT64_C(803040'9987141784), -10};
6688 const STAmount xrpBob{
XRP, UINT64_C(1092'878933)};
6690 USD, UINT64_C(3'988035892323031), -28};
6693 [&](
AMM& amm,
Env& env) {
6695 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(
XRP, USD);
6698 env.
fund(xrpBob, bob);
6699 env.
trust(USD(1'000'000), bob);
6700 env(pay(gw, bob, iouBob));
6703 env(offer(bob,
XRP(6300), USD(100'000)));
6708 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
6710 {{xrpPool, iouPool}},
6713 {jtx::supported_amendments() | fixAMMv1_1});
6719 testcase(
"AMM Offer Blocked By LOB");
6720 using namespace jtx;
6726 Env env(*
this, features);
6728 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6730 env(offer(alice,
XRP(1), USD(0.01)));
6733 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6737 env(offer(carol, USD(0.49),
XRP(1)));
6740 if (!features[fixAMMv1_1])
6742 BEAST_EXPECT(amm.expectBalances(
6743 XRP(200'000), USD(100'000), amm.tokens()));
6745 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6748 env, carol, 1, {{Amounts{USD(0.49),
XRP(1)}}}));
6752 BEAST_EXPECT(amm.expectBalances(
6753 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6755 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6764 Env env(*
this, features);
6766 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6770 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6773 env(offer(carol, USD(0.49),
XRP(1)));
6777 BEAST_EXPECT(amm.expectBalances(
6778 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6785 Env env(*
this, features);
6786 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6790 env(offer(bob, USD(1),
XRPAmount(500)));
6792 AMM amm(env, alice,
XRP(1'000), USD(500));
6793 env(offer(carol,
XRP(100), USD(55)));
6795 if (!features[fixAMMv1_1])
6798 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
6800 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6802 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
6806 BEAST_EXPECT(amm.expectBalances(
6808 STAmount{USD, UINT64_C(550'000000055), -9},
6816 STAmount{USD, 4'99999995, -8}}}}));
6818 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6825 Env env(*
this, features);
6826 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6828 AMM amm(env, alice,
XRP(1'000), USD(500));
6829 env(offer(carol,
XRP(100), USD(55)));
6831 BEAST_EXPECT(amm.expectBalances(
6833 STAmount{USD, UINT64_C(550'000000055), -9},
6847 using namespace jtx;
6851 Env env(*
this, features);
6857 {USD(1'000'000'000)});
6858 AMM amm(env, gw,
XRP(2), USD(1));
6859 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6860 amm.deposit(carol,
IOUAmount{1'000'000});
6861 amm.withdrawAll(alice);
6862 amm.withdrawAll(carol);
6864 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
6865 auto const lpTokenBalance =
6866 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6868 lpToken ==
"1414.213562373095" &&
6869 lpTokenBalance ==
"1414.213562373");
6870 if (!features[fixAMMv1_1])
6873 BEAST_EXPECT(amm.ammExists());
6877 amm.withdrawAll(gw);
6878 BEAST_EXPECT(!amm.ammExists());
6884 for (
auto const& lp : {gw, bob})
6886 Env env(*
this, features);
6887 auto const ABC = gw[
"ABC"];
6891 {alice, carol, bob},
6893 {USD(1'000'000'000), ABC(1'000'000'000'000)});
6894 AMM amm(env, lp, ABC(2'000'000), USD(1));
6895 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6896 amm.deposit(carol,
IOUAmount{1'000'000});
6897 amm.withdrawAll(alice);
6898 amm.withdrawAll(carol);
6900 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
6901 auto const lpTokenBalance =
6902 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6904 lpToken ==
"1414.213562373095" &&
6905 lpTokenBalance ==
"1414.213562373");
6906 if (!features[fixAMMv1_1])
6909 BEAST_EXPECT(amm.ammExists());
6913 amm.withdrawAll(lp);
6914 BEAST_EXPECT(!amm.ammExists());
6921 Env env(*
this, features);
6922 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)});
6923 AMM amm(env, gw,
XRP(10), USD(10));
6924 amm.deposit(alice, 1'000);
6927 BEAST_EXPECT(res && !res.value());
6930 BEAST_EXPECT(res && !res.value());
6934 Env env(*
this, features);
6935 fund(env, gw, {alice},
XRP(1'000), {USD(1'000), EUR(1'000)});
6936 AMM amm(env, gw, EUR(10), USD(10));
6937 amm.deposit(alice, 1'000);
6940 BEAST_EXPECT(res && !res.value());
6943 BEAST_EXPECT(res && !res.value());
6947 Env env(*
this, features);
6949 auto const YAN = gw1[
"YAN"];
6950 fund(env, gw, {gw1},
XRP(1'000), {USD(1'000)});
6951 fund(env, gw1, {gw},
XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
6952 AMM amm(env, gw1, YAN(10), USD(10));
6953 amm.deposit(gw, 1'000);
6956 BEAST_EXPECT(res && !res.value());
6958 BEAST_EXPECT(res && !res.value());
6965 testcase(
"test clawback from AMM account");
6966 using namespace jtx;
6969 Env env(*
this, features);
6972 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
6977 if (!features[featureAMMClawback])
7003 Issue usd(USD.issue().currency, amm.ammAccount());
7012 testcase(
"test AMMDeposit with frozen assets");
7013 using namespace jtx;
7020 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7030 Env env(*
this, features);
7031 testAMMDeposit(env, [&](
AMM& amm) {
7045 Env env(*
this, features);
7046 testAMMDeposit(env, [&](
AMM& amm) {
7057 if (features[featureAMMClawback])
7062 Env env(*
this, features);
7063 testAMMDeposit(env, [&](
AMM& amm) {
7078 Env env(*
this, features);
7079 testAMMDeposit(env, [&](
AMM& amm) {
7094 testcase(
"Fix Reserve Check On Withdrawal");
7095 using namespace jtx;
7100 auto test = [&](
auto&& cb) {
7101 Env env(*
this, features);
7102 auto const starting_xrp =
7103 reserve(env, 2) + env.
current()->fees().base * 5;
7104 env.
fund(starting_xrp, gw);
7105 env.
fund(starting_xrp, alice);
7106 env.
trust(USD(2'000), alice);
7108 env(pay(gw, alice, USD(2'000)));
7110 AMM amm(env, gw, EUR(1'000), USD(1'000));
7111 amm.deposit(alice, USD(1));
7116 test([&](
AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
7119 test([&](
AMM& amm) {
7122 .asset1Out = EUR(0.1),
7123 .asset2Out = USD(0.1),
7127 .asset1Out = USD(0.1),
7128 .asset2Out = EUR(0.1),
7133 test([&](
AMM& amm) {
7135 .
account = alice, .asset1Out = EUR(0.1), .err = err});
7144 testInvalidInstance();
7145 testInstanceCreate();
7146 testInvalidDeposit(all);
7147 testInvalidDeposit(all - featureAMMClawback);
7149 testInvalidWithdraw();
7151 testInvalidFeeVote();
7155 testBid(all - fixAMMv1_1);
7156 testInvalidAMMPayment();
7157 testBasicPaymentEngine(all);
7158 testBasicPaymentEngine(all - fixAMMv1_1);
7159 testBasicPaymentEngine(all - fixReducedOffersV2);
7160 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
7165 testAMMAndCLOB(all);
7166 testAMMAndCLOB(all - fixAMMv1_1);
7167 testTradingFee(all);
7168 testTradingFee(all - fixAMMv1_1);
7169 testAdjustedTokens(all);
7170 testAdjustedTokens(all - fixAMMv1_1);
7175 testSelection(all - fixAMMv1_1);
7176 testFixDefaultInnerObj();
7178 testFixOverflowOffer(all);
7179 testFixOverflowOffer(all - fixAMMv1_1);
7181 testFixChangeSpotPriceQuality(all);
7182 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
7183 testFixAMMOfferBlockedByLOB(all);
7184 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
7185 testLPTokenBalance(all);
7186 testLPTokenBalance(all - fixAMMv1_1);
7187 testAMMClawback(all);
7188 testAMMClawback(all - featureAMMClawback);
7189 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
7190 testAMMDepositWithFrozenAssets(all);
7191 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
7192 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
7193 testFixReserveCheckOnWithdrawal(all);
7194 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.