21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
23#include <test/jtx/CaptureLogs.h>
24#include <test/jtx/Env.h>
25#include <test/jtx/amount.h>
26#include <test/jtx/sendmax.h>
28#include <xrpld/app/misc/AMMHelpers.h>
29#include <xrpld/app/misc/AMMUtils.h>
30#include <xrpld/app/paths/AMMContext.h>
31#include <xrpld/app/tx/detail/AMMBid.h>
33#include <xrpl/basics/Number.h>
34#include <xrpl/protocol/AMMCore.h>
35#include <xrpl/protocol/Feature.h>
36#include <xrpl/protocol/TER.h>
38#include <boost/regex.hpp>
88 {{
USD(20'000),
BTC(0.5)}});
139 BEAST_EXPECT(amm.expectTradingFee(1'000));
140 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
167 BEAST_EXPECT(!ammAlice.ammExists());
176 BEAST_EXPECT(!ammAlice.ammExists());
184 BEAST_EXPECT(!ammAlice.ammExists());
201 BEAST_EXPECT(!ammAlice.ammExists());
210 BEAST_EXPECT(!ammAlice.ammExists());
219 BEAST_EXPECT(!ammAlice.ammExists());
238 BEAST_EXPECT(!ammAlice.ammExists());
263 BEAST_EXPECT(!ammAlice.ammExists());
328 auto const starting_xrp =
330 env.
fund(starting_xrp,
gw);
345 auto const starting_xrp =
347 env.
fund(starting_xrp,
gw);
402 auto const token1 = ammAlice.
lptIssue();
403 auto const token2 = ammAlice1.lptIssue();
428 auto const USD1 = gw1[
"USD"];
625 for (
auto const& it : invalidOptions)
644 jv[jss::TransactionType] = jss::AMMDeposit;
666 jv[jss::TransactionType] = jss::AMMDeposit;
671 jv[jss::LPTokenOut] =
852 [&](
AMM& ammAlice,
Env& env) {
853 auto const enabledv1_3 =
854 env.
current()->rules().enabled(fixAMMv1_3);
876 {features, features - fixAMMv1_3});
887 [&](
AMM& ammAlice,
Env& env) {
889 if (!features[featureAMMClawback])
933 [&](
AMM& ammAlice,
Env& env) {
936 if (!features[featureAMMClawback])
995 [&](
AMM& ammAlice,
Env& env) {
1035 {{
USD(20'000),
BTC(0.5)}});
1039 Env env(*
this, features);
1054 if (features[featureAMMClawback])
1143 auto const starting_xrp =
1178 auto const starting_xrp =
1343 [&](
AMM& amm,
Env& env) {
1349 {.pool = {{
USD(1'000'000),
XRP(1'000'000)}},
1350 .features = {features - fixAMMv1_3}});
1387 using namespace jtx;
1392 auto const baseFee = env.
current()->fees().base;
1406 for (
Number const deltaLPTokens :
1407 {
Number{UINT64_C(100000'0000000009), -10},
1408 Number{UINT64_C(100000'0000000001), -10}})
1414 deltaLPTokens.
mantissa(), deltaLPTokens.exponent()};
1427 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1428 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1432 Number const fr = deltaLPTokens / 1e7;
1436 Number const deltaXRP = fr * 1e10;
1437 Number const deltaUSD = fr * 1e4;
1447 XRP(10'000) + depositXRP,
1448 USD(10'000) + depositUSD,
1572 STAmount{USD, UINT64_C(10'000'000001), -6},
1594 for (
auto const& feat : {
all,
all - fixAMMv1_3})
1596 Env env(*
this, feat);
1689 using namespace jtx;
1693 [&](
AMM& ammAlice,
Env& env) {
1703 [&](
AMM& ammAlice,
Env& env) {
1729 .asset1Out =
USD(100),
1858 for (
auto const& it : invalidOptions)
1869 ter(std::get<5>(it)));
2010 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
2017 [&](
AMM& ammAlice,
Env& env) {
2040 {
all,
all - fixAMMv1_3});
2043 [&](
AMM& ammAlice,
Env& env) {
2065 {
all,
all - fixAMMv1_3});
2131 [&](
AMM& ammAlice,
Env& env) {
2133 auto const err = env.
enabled(fixAMMv1_3)
2142 {
all,
all - fixAMMv1_3});
2170 [&](
AMM& ammAlice,
Env&) {
2182 [&](
AMM& ammAlice,
Env&) {
2208 [&](
AMM& ammAlice,
Env& env) {
2209 auto const err = env.
enabled(fixAMMv1_3)
2219 {.features = {
all,
all - fixAMMv1_3}, .noLog =
true});
2269 using namespace jtx;
2275 auto const baseFee = env.
current()->fees().base.drops();
2326 [&](
AMM& ammAlice,
Env& env) {
2342 {
all,
all - fixAMMv1_3});
2394 [&](
AMM& ammAlice,
Env& env) {
2417 {
all,
all - fixAMMv1_3});
2462 [&](
AMM& ammAlice,
Env& env) {
2478 else if (env.
enabled(fixAMMv1_3))
2486 {.features = {
all,
all - fixAMMv1_3,
all - fixAMMv1_1 - fixAMMv1_3},
2491 [&](
AMM& ammAlice,
Env& env) {
2507 else if (env.
enabled(fixAMMv1_3))
2516 {
all,
all - fixAMMv1_3,
all - fixAMMv1_1 - fixAMMv1_3});
2552 STAmount{USD, UINT64_C(9'999'999999), -6},
2556 [&](
AMM& ammAlice,
Env& env) {
2573 {
all,
all - fixAMMv1_3});
2621 using namespace jtx;
2690 using namespace jtx;
2696 ammAlice.
vote({}, 1'000);
2702 auto vote = [&](
AMM& ammAlice,
2705 int fundUSD = 100'000,
2715 fund(env,
gw, {a}, {
USD(fundUSD)}, Fund::Acct);
2717 ammAlice.
vote(a, 50 * (i + 1));
2724 [&](
AMM& ammAlice,
Env& env) {
2725 for (
int i = 0; i < 7; ++i)
2726 vote(ammAlice, env, i, 10'000);
2737 for (
int i = 0; i < 7; ++i)
2738 vote(ammAlice, env, i);
2741 ammAlice.
vote(a, 450);
2748 for (
int i = 0; i < 7; ++i)
2749 vote(ammAlice, env, i);
2751 vote(ammAlice, env, 7, 100'000, 20'000'000);
2758 for (
int i = 7; i > 0; --i)
2759 vote(ammAlice, env, i);
2761 vote(ammAlice, env, 0, 100'000, 20'000'000);
2770 for (
int i = 0; i < 7; ++i)
2771 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2773 for (
int i = 0; i < 7; ++i)
2787 for (
int i = 0; i < 7; ++i)
2788 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2790 for (
int i = 0; i < 7; ++i)
2791 ammAlice.
withdraw(accounts[i], 9'000'000);
2795 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2797 BEAST_EXPECT(info[i][jss::account] !=
carol.
human());
2807 using namespace jtx;
2814 AMM amm(env,
gw,
XRP(1'000),
USD(1'000),
false, 1'000);
2817 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2824 .bidMin = 1'000'000,
2833 AMM amm(env,
gw,
XRP(1'000),
USD(1'000),
false, 1'000);
2836 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2843 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2849 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2856 BEAST_EXPECT(
Number{amm.getLPTokensBalance(
gw)} == 1);
2873 .flags = tfWithdrawAll,
2879 for (
auto bid : {0, -100})
2912 Account
const dan(
"dan");
2928 .authAccounts = {bob},
2936 .assets = {{USD, GBP}},
2943 .bidMax = STAmount{USD, 100},
2948 .bidMin = STAmount{USD, 100},
2950 ter(temBAD_AMM_TOKENS));
2954 testAMM([&](AMM& ammAlice, Env& env) {
2955 ammAlice.withdrawAll(alice);
2964 testAMM([&](AMM& ammAlice, Env& env) {
2966 Account bill(
"bill");
2967 Account scott(
"scott");
2968 Account james(
"james");
2969 env.fund(
XRP(1'000), bob, ed, bill, scott, james);
2971 ammAlice.deposit(carol, 1'000'000);
2975 .authAccounts = {bob, ed, bill, scott, james},
2981 testAMM([&](AMM& ammAlice, Env& env) {
2982 fund(env, gw, {bob},
XRP(1'000), {USD(100)}, Fund::Acct);
2983 ammAlice.deposit(carol, 1'000'000);
2984 ammAlice.deposit(bob, 10);
2987 .bidMin = 1'000'001,
2989 ter(tecAMM_INVALID_TOKENS));
2992 .bidMax = 1'000'001,
2994 ter(tecAMM_INVALID_TOKENS));
2999 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
3004 ter(tecAMM_INVALID_TOKENS));
3010 fund(env, gw, {alice, bob},
XRP(1'000), {USD(1'000)});
3012 auto const lpIssue =
amm.lptIssue();
3013 env.trust(STAmount{lpIssue, 100}, alice);
3014 env.trust(STAmount{lpIssue, 50}, bob);
3015 env(
pay(gw, alice, STAmount{lpIssue, 100}));
3016 env(
pay(gw, bob, STAmount{lpIssue, 50}));
3017 env(
amm.bid({.account = alice, .bidMin = 100}));
3024 ter(tecAMM_FAILED));
3032 using namespace jtx;
3039 [&](
AMM& ammAlice,
Env& env) {
3040 ammAlice.
deposit(carol, 1'000'000);
3041 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
3054 [&](
AMM& ammAlice,
Env& env) {
3055 ammAlice.
deposit(carol, 1'000'000);
3058 {.account = carol, .bidMin = 110, .bidMax = 110}));
3064 {.account = alice, .bidMin = 180, .bidMax = 200}));
3067 XRP(11'000), USD(11'000),
IOUAmount{10'999'814'5, -1}));
3076 [&](
AMM& ammAlice,
Env& env) {
3077 ammAlice.
deposit(carol, 1'000'000);
3079 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
3082 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
3083 ammAlice.
deposit(bob, 1'000'000);
3085 env(ammAlice.
bid({.account = bob}));
3096 env(ammAlice.
bid({.account = carol, .bidMax = 600}));
3110 {.account = carol, .bidMin = 100, .bidMax = 600}));
3121 [&](
AMM& ammAlice,
Env& env) {
3122 ammAlice.
deposit(carol, 1'000'000);
3124 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
3125 ammAlice.
deposit(bob, 1'000'000);
3126 if (!features[fixAMMv1_3])
3136 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
3140 env(ammAlice.
bid({.account = bob}));
3146 env(ammAlice.
bid({.account = carol}));
3152 env(ammAlice.
bid({.account = bob}));
3158 0, std::nullopt,
IOUAmount{127'33875, -5}));
3161 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
3165 if (!features[fixAMMv1_3])
3186 [&](
AMM& ammAlice,
Env& env) {
3189 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
3190 ammAlice.
deposit(bob, 1'000'000);
3191 ammAlice.
deposit(ed, 1'000'000);
3192 ammAlice.
deposit(carol, 500'000);
3193 ammAlice.
deposit(dan, 500'000);
3198 .authAccounts = {bob, ed},
3200 auto const slotPrice =
IOUAmount{5'200};
3201 ammTokens -= slotPrice;
3203 if (!features[fixAMMv1_3])
3205 XRP(13'000), USD(13'000), ammTokens));
3208 XRPAmount{13'000'000'003}, USD(13'000), ammTokens));
3210 for (
int i = 0; i < 10; ++i)
3212 auto tokens = ammAlice.
deposit(carol, USD(100));
3213 ammAlice.
withdraw(carol, tokens, USD(0));
3214 tokens = ammAlice.
deposit(bob, USD(100));
3215 ammAlice.
withdraw(bob, tokens, USD(0));
3216 tokens = ammAlice.
deposit(ed, USD(100));
3217 ammAlice.
withdraw(ed, tokens, USD(0));
3220 if (!features[fixAMMv1_1])
3224 STAmount(USD, UINT64_C(29'499'00572620545), -11));
3227 STAmount(USD, UINT64_C(18'999'00572616195), -11));
3230 STAmount(USD, UINT64_C(18'999'00572611841), -11));
3234 STAmount(USD, UINT64_C(13'002'98282151419), -11),
3241 STAmount(USD, UINT64_C(29'499'00572620544), -11));
3244 STAmount(USD, UINT64_C(18'999'00572616194), -11));
3247 STAmount(USD, UINT64_C(18'999'0057261184), -10));
3249 if (!features[fixAMMv1_3])
3252 STAmount(USD, UINT64_C(13'002'98282151422), -11),
3257 STAmount(USD, UINT64_C(13'002'98282151422), -11),
3262 for (
int i = 0; i < 10; ++i)
3264 auto const tokens = ammAlice.
deposit(dan, USD(100));
3265 ammAlice.
withdraw(dan, tokens, USD(0));
3270 if (!features[fixAMMv1_1])
3274 STAmount(USD, UINT64_C(19'490'056722744), -9));
3278 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3281 ammAlice.
deposit(carol, USD(100));
3285 STAmount{USD, UINT64_C(13'112'92609877019), -11},
3287 env(pay(carol, bob, USD(100)),
3295 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3300 if (!features[fixAMMv1_3])
3303 STAmount(USD, UINT64_C(19'490'05672274399), -11));
3307 STAmount(USD, UINT64_C(19'490'05672274398), -11));
3309 if (!features[fixAMMv1_3])
3312 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3317 STAmount{USD, UINT64_C(13'012'92609877024), -11},
3320 ammAlice.
deposit(carol, USD(100));
3322 if (!features[fixAMMv1_3])
3325 STAmount{USD, UINT64_C(13'112'92609877023), -11},
3330 STAmount{USD, UINT64_C(13'112'92609877024), -11},
3332 env(pay(carol, bob, USD(100)),
3338 if (!features[fixAMMv1_3])
3341 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3346 STAmount{USD, UINT64_C(13'012'92609877024), -11},
3355 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3359 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3362 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3366 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3373 STAmount{USD, UINT64_C(13'114'03663044937), -11},
3380 if (!features[fixAMMv1_1])
3383 STAmount(USD, UINT64_C(29'399'00572620545), -11));
3384 else if (!features[fixAMMv1_3])
3387 STAmount(USD, UINT64_C(29'399'00572620544), -11));
3389 for (
int i = 0; i < 10; ++i)
3391 auto const tokens = ammAlice.
deposit(carol, USD(100));
3392 ammAlice.
withdraw(carol, tokens, USD(0));
3396 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3400 STAmount(USD, UINT64_C(29'389'06197177128), -11));
3403 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3406 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3410 STAmount(USD, UINT64_C(29'389'06197177124), -11));
3413 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3420 STAmount(USD, UINT64_C(29'389'06197177129), -11));
3423 STAmount{USD, UINT64_C(13'123'98038488352), -11},
3431 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
3435 STAmount{USD, UINT64_C(13'023'98038490681), -11},
3438 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
3442 STAmount{USD, UINT64_C(13'023'98038490689), -11},
3449 STAmount{USD, UINT64_C(13'023'98038488352), -11},
3460 [&](AMM& ammAlice, Env& env) {
3463 Number{STAmount::cMinValue, STAmount::cMinOffset};
3465 {.account = alice, .bidMin = IOUAmount{tiny}}));
3468 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
3470 BEAST_EXPECT(ammAlice.expectBalances(
3471 XRP(10'000), USD(10'000), ammAlice.tokens()));
3476 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
3479 BEAST_EXPECT(ammAlice.expectAuctionSlot(
3480 0, 0, IOUAmount{tiny * Number{105, -2}}));
3483 BEAST_EXPECT(ammAlice.expectBalances(
3484 XRP(10'000), USD(10'000), ammAlice.tokens()));
3493 [&](AMM& ammAlice, Env& env) {
3496 .bidMin = IOUAmount{100},
3497 .authAccounts = {carol},
3499 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
3500 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
3501 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
3504 fund(env, {bob, dan},
XRP(1'000));
3507 .bidMin = IOUAmount{100},
3508 .authAccounts = {bob, dan},
3510 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
3519 Env env(*
this, features);
3520 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3521 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3522 auto const lpIssue =
amm.lptIssue();
3523 env.trust(STAmount{lpIssue, 500}, alice);
3524 env.trust(STAmount{lpIssue, 50}, bob);
3525 env(
pay(gw, alice, STAmount{lpIssue, 500}));
3526 env(
pay(gw, bob, STAmount{lpIssue, 50}));
3528 env(
amm.bid({.account = alice, .bidMin = 500}));
3529 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3530 BEAST_EXPECT(
expectLine(env, alice, STAmount{lpIssue, 0}));
3533 env(
pay(alice, bob, USD(10)), path(~USD), sendmax(
XRP(11)));
3534 BEAST_EXPECT(
amm.expectBalances(
3535 XRPAmount{1'010'010'011},
3537 IOUAmount{1'004'487'562112089, -9}));
3539 env(
pay(bob, alice,
XRP(10)), path(~XRP), sendmax(USD(11)));
3540 if (!features[fixAMMv1_1])
3542 BEAST_EXPECT(
amm.expectBalances(
3543 XRPAmount{1'000'010'011},
3544 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3545 IOUAmount{1'004'487'562112089, -9}));
3549 BEAST_EXPECT(
amm.expectBalances(
3550 XRPAmount{1'000'010'011},
3551 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3552 IOUAmount{1'004'487'562112089, -9}));
3558 Env env(*
this, features);
3559 auto const baseFee = env.current()->fees().base;
3561 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3562 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3566 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3567 env.app().config().features.erase(featureAMM);
3568 PreflightContext pfctx(
3571 env.current()->rules(),
3574 auto pf = AMMBid::preflight(pfctx);
3575 BEAST_EXPECT(pf == temDISABLED);
3576 env.app().config().features.insert(featureAMM);
3580 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3581 jtx.jv[
"TxnSignature"] =
"deadbeef";
3582 jtx.stx = env.ust(jtx);
3583 PreflightContext pfctx(
3586 env.current()->rules(),
3589 auto pf = AMMBid::preflight(pfctx);
3590 BEAST_EXPECT(pf != tesSUCCESS);
3594 auto jtx = env.jt(tx, seq(1), fee(baseFee));
3595 jtx.jv[
"Asset2"][
"currency"] =
"XRP";
3596 jtx.jv[
"Asset2"].removeMember(
"issuer");
3597 jtx.stx = env.ust(jtx);
3598 PreflightContext pfctx(
3601 env.current()->rules(),
3604 auto pf = AMMBid::preflight(pfctx);
3605 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3613 testcase(
"Invalid AMM Payment");
3614 using namespace jtx;
3620 for (
auto const& acct : {gw, alice})
3624 fund(env, gw, {alice, carol},
XRP(1'000), {USD(100)});
3626 AMM ammAlice(env, acct,
XRP(10), USD(10));
3628 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3631 env(pay(carol, ammAlice.ammAccount(),
XRP(300)),
3634 env(pay(carol, ammAlice.ammAccount(), USD(10)),
3639 fund(env, gw, {alice, carol},
XRP(10'000'000), {USD(10'000)});
3641 AMM ammAlice(env, acct,
XRP(1'000'000), USD(100));
3643 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3646 env(pay(carol, ammAlice.ammAccount(),
XRP(1'000'000)),
3652 testAMM([&](
AMM& ammAlice,
Env& env) {
3653 auto const baseFee = env.
current()->fees().base;
3663 testAMM([&](
AMM& ammAlice,
Env& env) {
3664 auto const pk = carol.pk();
3665 auto const settleDelay = 100s;
3667 env.
current()->info().parentCloseTime + 200s;
3679 testAMM([&](
AMM& ammAlice,
Env& env) {
3686 [&](
AMM& ammAlice,
Env& env) {
3688 env(pay(alice, carol, USD(100)),
3692 env(pay(alice, carol,
XRP(100)),
3699 STAmount{USD, UINT64_C(99'999999999), -9}),
3705 STAmount{USD, UINT64_C(999'99999999), -8}),
3714 env(pay(alice, carol, USD(99.99)),
3723 {{
XRP(100), USD(100)}});
3726 testAMM([&](
AMM& ammAlice,
Env& env) {
3729 env(pay(alice, carol, USD(1)),
3734 env(pay(alice, carol,
XRP(1)),
3742 testAMM([&](
AMM& ammAlice,
Env& env) {
3748 env(pay(alice, carol, USD(1)),
3753 env(pay(alice, carol,
XRP(1)),
3761 testAMM([&](
AMM& ammAlice,
Env& env) {
3765 env(pay(alice, carol,
XRP(1)),
3776 testcase(
"Basic Payment");
3777 using namespace jtx;
3782 [&](
AMM& ammAlice,
Env& env) {
3783 env.
fund(jtx::XRP(30'000), bob);
3785 env(pay(bob, carol, USD(100)),
3791 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3793 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3796 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3798 {{
XRP(10'000), USD(10'100)}},
3805 [&](
AMM& ammAlice,
Env& env) {
3806 env.
fund(jtx::XRP(30'000), bob);
3808 env(pay(bob, carol, USD(100)),
sendmax(
XRP(100)));
3811 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3813 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3816 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3818 {{
XRP(10'000), USD(10'100)}},
3826 [&](
AMM& ammAlice,
Env& env) {
3827 env.
fund(jtx::XRP(30'000), bob);
3832 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3834 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3837 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3839 {{
XRP(10'000), USD(10'100)}},
3846 [&](
AMM& ammAlice,
Env& env) {
3847 env.
fund(jtx::XRP(30'000), bob);
3851 env(pay(bob, carol, USD(100)),
3858 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3860 BEAST_EXPECT(
expectLine(env, carol, USD(30'010)));
3864 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3868 env(pay(bob, carol, USD(100)),
3876 {{
XRP(10'000), USD(10'010)}},
3883 [&](
AMM& ammAlice,
Env& env) {
3886 env.
fund(jtx::XRP(30'000), bob);
3891 env(pay(bob, carol, USD(100)),
3898 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3903 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
3905 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3907 {{
XRP(10'000), USD(10'010)}},
3914 [&](
AMM& ammAlice,
Env& env) {
3915 env.
fund(jtx::XRP(30'000), bob);
3917 env(pay(bob, carol, USD(100)),
3923 {{
XRP(10'000), USD(10'000)}},
3933 Env env(*
this, features);
3935 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3939 auto ammEUR_XRP =
AMM(env, alice,
XRP(10'000), EUR(10'000));
3940 auto ammUSD_EUR =
AMM(env, alice, EUR(10'000), USD(10'000));
3943 env(pay(bob, carol, USD(100)),
3948 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3950 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3951 ammEUR_XRP.tokens()));
3952 if (!features[fixAMMv1_1])
3954 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3955 STAmount(USD, UINT64_C(9'970'097277662122), -12),
3956 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3957 ammUSD_EUR.tokens()));
3960 Amounts
const expectedAmounts =
3961 env.
closed()->rules().enabled(fixReducedOffersV2)
3962 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233787816), -14)}
3965 STAmount(USD, UINT64_C(29'90272233787818), -14)};
3967 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3971 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3972 STAmount(USD, UINT64_C(9'970'097277662172), -12),
3973 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3974 ammUSD_EUR.tokens()));
3977 Amounts
const expectedAmounts =
3978 env.
closed()->rules().enabled(fixReducedOffersV2)
3979 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233782839), -14)}
3982 STAmount(USD, UINT64_C(29'90272233782840), -14)};
3984 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
4000 [&](
AMM& ammAlice,
Env& env) {
4003 env.
trust(EUR(2'000), alice);
4005 env(pay(gw, alice, EUR(1'000)));
4010 env(pay(bob, carol, USD(100)),
4017 STAmount(USD, UINT64_C(9'950'01249687578), -11),
4025 STAmount(EUR, UINT64_C(49'98750312422), -11)},
4027 STAmount(EUR, UINT64_C(49'98750312422), -11),
4028 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
4033 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4050 [&](
AMM& ammAlice,
Env& env) {
4051 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
4055 env(pay(alice, carol, USD(200)),
4059 if (!features[fixAMMv1_1])
4062 XRP(10'100), USD(10'000), ammAlice.
tokens()));
4064 BEAST_EXPECT(
expectLine(env, carol, USD(30'200)));
4070 STAmount(USD, UINT64_C(10'000'00000000001), -11),
4075 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
4083 ammCrtFee(env) -
txfee(env, 1)));
4086 {{
XRP(10'000), USD(10'100)}},
4095 Env env(*
this, features);
4096 fund(env, gw, {alice, bob, carol},
XRP(20'000), {USD(2'000)});
4100 AMM ammAlice(env, alice,
XRP(1'000), USD(1'050));
4101 env(pay(alice, carol, USD(200)),
4106 XRP(1'050), USD(1'000), ammAlice.
tokens()));
4107 BEAST_EXPECT(
expectLine(env, carol, USD(2'200)));
4113 [&](
AMM& ammAlice,
Env& env) {
4114 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
4116 env(offer(bob, USD(100),
XRP(100)));
4119 XRP(10'100), USD(10'000), ammAlice.
tokens()));
4121 BEAST_EXPECT(
expectLine(env, bob, USD(1'100)));
4124 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
4127 {{
XRP(10'000), USD(10'100)}},
4135 [&](
AMM& ammAlice,
Env& env) {
4136 env(rate(gw, 1.25));
4142 env(offer(carol, EUR(100), GBP(100)));
4146 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4148 BEAST_EXPECT(
expectLine(env, carol, GBP(29'875)));
4150 BEAST_EXPECT(
expectLine(env, carol, EUR(30'100)));
4153 {{GBP(1'000), EUR(1'100)}},
4159 [&](
AMM& amm,
Env& env) {
4160 env(rate(gw, 1.001));
4162 env(offer(carol,
XRP(100), USD(55)));
4164 if (!features[fixAMMv1_1])
4174 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
4176 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
4187 BEAST_EXPECT(amm.expectBalances(
4189 STAmount{USD, UINT64_C(550'000000055), -9},
4198 STAmount{USD, 4'99999995, -8}}}}));
4202 STAmount(USD, UINT64_C(29'949'94999999494), -11));
4205 {{
XRP(1'000), USD(500)}},
4210 [&](
AMM& amm,
Env& env) {
4211 env(rate(gw, 1.001));
4213 env(offer(carol,
XRP(10), USD(5.5)));
4215 if (!features[fixAMMv1_1])
4217 BEAST_EXPECT(amm.expectBalances(
4219 STAmount{USD, UINT64_C(505'050505050505), -12},
4225 BEAST_EXPECT(amm.expectBalances(
4227 STAmount{USD, UINT64_C(505'0505050505051), -13},
4232 {{
XRP(1'000), USD(500)}},
4238 [&](
AMM& ammAlice,
Env& env) {
4245 {GBP(2'000), EUR(2'000)},
4247 env(rate(gw, 1.25));
4258 env(offer(carol, EUR(100), GBP(100)));
4260 if (!features[fixAMMv1_1])
4269 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
4270 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
4278 STAmount{EUR, UINT64_C(50'684828792831), -12},
4279 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
4291 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
4296 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
4308 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
4309 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
4317 STAmount{EUR, UINT64_C(27'06583722134028), -14},
4318 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
4330 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
4335 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
4338 BEAST_EXPECT(
expectLine(env, bob, GBP(2'010)));
4340 BEAST_EXPECT(
expectLine(env, ed, EUR(1'987.5)));
4342 {{GBP(1'000), EUR(1'100)}},
4355 [&](
AMM& ammAlice,
Env& env) {
4356 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
4357 env(rate(gw, 1.25));
4359 env(pay(bob, carol, EUR(100)),
4365 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4367 BEAST_EXPECT(
expectLine(env, carol, EUR(30'080)));
4369 {{GBP(1'000), EUR(1'100)}},
4386 [&](
AMM& ammAlice,
Env& env) {
4389 auto const CAN = gw[
"CAN"];
4390 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
4391 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
4392 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
4393 env(trust(carol, USD(100)));
4394 env(rate(gw, 1.25));
4396 env(offer(dan, CAN(200), GBP(200)));
4397 env(offer(ed, EUR(200), USD(200)));
4399 env(pay(bob, carol, USD(100)),
4400 path(~GBP, ~EUR, ~USD),
4405 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25), GBP(43.75)));
4407 GBP(10'125), EUR(10'000), ammAlice.
tokens()));
4408 BEAST_EXPECT(
expectLine(env, ed, EUR(300), USD(100)));
4409 BEAST_EXPECT(
expectLine(env, carol, USD(80)));
4411 {{GBP(10'000), EUR(10'125)}},
4418 [&](
AMM& ammAlice,
Env& env) {
4419 env(pay(alice, carol, USD(99.99)),
4424 env(pay(alice, carol, USD(100)),
4429 env(pay(alice, carol,
XRP(100)),
4440 {{
XRP(100), USD(100)}},
4447 Env env(*
this, features);
4448 auto const ETH = gw[
"ETH"];
4454 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4455 fund(env, gw, {carol, bob},
XRP(1'000), {USD(200)}, Fund::Acct);
4456 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4457 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4458 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4459 AMM xrp_usd(env, alice,
XRP(10'150), USD(10'200));
4460 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4461 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4462 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
4463 env(pay(bob, carol, USD(100)),
4464 path(~EUR, ~BTC, ~USD),
4466 path(~ETH, ~EUR, ~USD),
4468 if (!features[fixAMMv1_1])
4474 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
4477 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
4478 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4481 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4482 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4488 STAmount{USD, UINT64_C(10'126'06848287914), -11},
4495 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
4498 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
4499 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4502 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4503 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4509 STAmount{USD, UINT64_C(10'126'06848287943), -11},
4519 BEAST_EXPECT(xrp_eur.expectBalances(
4520 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
4522 EUR(10'000), BTC(10'200), eur_btc.
tokens()));
4524 BTC(10'100), USD(10'000), btc_usd.
tokens()));
4526 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4531 Env env(*
this, features);
4532 auto const ETH = gw[
"ETH"];
4538 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4539 fund(env, gw, {carol, bob},
XRP(1000), {USD(200)}, Fund::Acct);
4540 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4541 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4542 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4543 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4544 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4545 env(pay(bob, carol, USD(100)),
4546 path(~EUR, ~BTC, ~USD),
4547 path(~ETH, ~EUR, ~BTC, ~USD),
4549 if (!features[fixAMMv1_1])
4553 BEAST_EXPECT(xrp_eur.expectBalances(
4555 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
4558 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4559 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4562 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
4567 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
4570 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
4571 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4576 BEAST_EXPECT(xrp_eur.expectBalances(
4578 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
4581 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4582 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4585 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
4590 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
4593 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
4594 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4597 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4603 [&](
AMM& ammAlice,
Env& env) {
4605 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4606 env(trust(alice, EUR(200)));
4607 for (
int i = 0; i < 30; ++i)
4608 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4611 env(offer(alice, EUR(140),
XRP(100)));
4612 env(pay(bob, carol, USD(100)),
4616 if (!features[fixAMMv1_1])
4621 STAmount{USD, UINT64_C(9'970'089730807577), -12},
4626 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
4632 STAmount{USD, UINT64_C(9'970'089730807827), -12},
4637 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
4648 [&](
AMM& ammAlice,
Env& env) {
4650 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4651 env(trust(alice, EUR(200)));
4652 for (
int i = 0; i < 29; ++i)
4653 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4656 env(offer(alice, EUR(140),
XRP(100)));
4657 env(pay(bob, carol, USD(100)),
4663 if (!features[fixAMMv1_1])
4669 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4673 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4679 {{{
STAmount{EUR, UINT64_C(39'1858572), -7},
4690 Env env(*
this, features);
4691 fund(env, gw, {alice, carol, bob},
XRP(30'000), {USD(30'000)});
4692 env(offer(bob,
XRP(100), USD(100.001)));
4693 AMM ammAlice(env, alice,
XRP(10'000), USD(10'100));
4694 env(offer(carol, USD(100),
XRP(100)));
4695 if (!features[fixAMMv1_1])
4699 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4706 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
4712 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4719 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
4720 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4726 [&](
AMM& ammAlice,
Env& env) {
4730 env(pay(alice, carol, USD(1)),
4745 testcase(
"AMM Tokens");
4746 using namespace jtx;
4749 testAMM([&](
AMM& ammAlice,
Env& env) {
4750 auto const baseFee = env.
current()->fees().base.drops();
4751 auto const token1 = ammAlice.
lptIssue();
4758 env(offer(carol,
STAmount{token1, 5'000'000}, priceXRP));
4760 env(offer(alice, priceXRP,
STAmount{token1, 5'000'000}));
4768 ammAlice.
vote(carol, 1'000);
4770 ammAlice.
vote(carol, 0);
4773 env(ammAlice.
bid({.account = carol, .bidMin = 100}));
4798 testAMM([&](
AMM& ammAlice,
Env& env) {
4799 ammAlice.
deposit(carol, 1'000'000);
4800 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
4801 AMM ammAlice1(env, alice,
XRP(10'000), EUR(10'000));
4802 ammAlice1.deposit(carol, 1'000'000);
4803 auto const token1 = ammAlice.
lptIssue();
4804 auto const token2 = ammAlice1.lptIssue();
4824 testAMM([&](
AMM& ammAlice,
Env& env) {
4825 auto const token1 = ammAlice.
lptIssue();
4828 ammAlice.
deposit(carol, 1'000'000);
4834 env(pay(alice, carol,
STAmount{token1, 100}));
4844 env(pay(carol, alice,
STAmount{token1, 100}));
4856 testcase(
"Amendment");
4857 using namespace jtx;
4862 all - featureAMM - fixUniversalNumber};
4864 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
4866 Env env{*
this, feature};
4867 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
4885 using namespace jtx;
4887 testAMM([&](
AMM& ammAlice,
Env& env) {
4888 auto const info = env.
rpc(
4892 "{\"account\": \"" + to_string(ammAlice.
ammAccount()) +
4895 info[jss::result][jss::account_data][jss::Flags].asUInt();
4905 testcase(
"Rippling");
4906 using namespace jtx;
4922 auto const TSTA = A[
"TST"];
4923 auto const TSTB = B[
"TST"];
4932 env.
trust(TSTA(10'000), C);
4933 env.
trust(TSTB(10'000), C);
4934 env(pay(A, C, TSTA(10'000)));
4935 env(pay(B, C, TSTB(10'000)));
4936 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
4937 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
4944 env(pay(C, D,
STAmount{ammIss, 10}),
4946 path(amm.ammAccount()),
4955 testcase(
"AMMAndCLOB, offer quality change");
4956 using namespace jtx;
4957 auto const gw =
Account(
"gw");
4958 auto const TST = gw[
"TST"];
4959 auto const LP1 =
Account(
"LP1");
4960 auto const LP2 =
Account(
"LP2");
4962 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4963 Env env(*
this, features);
4964 env.
fund(
XRP(30'000'000'000), gw);
4965 env(offer(gw,
XRP(11'500'000'000), TST(1'000'000'000)));
4969 env(offer(LP1, TST(25),
XRPAmount(287'500'000)));
4974 env(offer(LP2, TST(25),
XRPAmount(287'500'000)));
4986 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
4992 lp2TakerGets = offer[
"taker_gets"].asString();
4993 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
4998 if (!features[fixAMMv1_1])
5002 STAmount{TST, UINT64_C(1'68737984885388), -14}),
5008 STAmount{TST, UINT64_C(1'68737976189735), -14}),
5017 BEAST_EXPECT(lp2TakerGets == offer[
"taker_gets"].asString());
5019 lp2TakerPays == offer[
"taker_pays"][
"value"].asString());
5026 testcase(
"Trading Fee");
5027 using namespace jtx;
5031 [&](
AMM& ammAlice,
Env& env) {
5033 ammAlice.
deposit(carol, USD(3'000));
5037 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5039 ammAlice.
vote(alice, 1'000);
5043 ammAlice.
deposit(carol, USD(3'000));
5045 carol,
IOUAmount{994'981155689671, -12}));
5046 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
5048 ammAlice.
vote(alice, 0);
5054 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
5056 {{USD(1'000), EUR(1'000)}},
5064 [&](
AMM& ammAlice,
Env& env) {
5066 auto tokensFee = ammAlice.
deposit(
5067 carol, USD(1'000), std::nullopt,
STAmount{USD, 1, -1});
5070 ammAlice.
vote(alice, 0);
5072 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
5075 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
5076 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
5086 [&](
AMM& ammAlice,
Env& env) {
5088 auto const tokensFee = ammAlice.
deposit(
5089 carol, USD(200), std::nullopt,
STAmount{USD, 2020, -6});
5092 ammAlice.
vote(alice, 0);
5094 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
5097 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
5098 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
5107 [&](
AMM& ammAlice,
Env& env) {
5109 ammAlice.
deposit(carol, USD(3'000));
5112 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
5114 ammAlice.
vote(alice, 1'000);
5121 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
5123 {{USD(1'000), EUR(1'000)}},
5130 [&](
AMM& ammAlice,
Env& env) {
5131 ammAlice.
deposit(carol, 1'000'000);
5132 auto const tokensFee = ammAlice.
withdraw(
5133 carol, USD(100), std::nullopt,
IOUAmount{520, 0});
5135 auto const balanceAfterWithdraw = [&]() {
5136 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5137 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
5138 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5139 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
5141 return STAmount(USD, UINT64_C(30'443'43891402713), -11);
5143 BEAST_EXPECT(env.
balance(carol, USD) == balanceAfterWithdraw);
5145 auto const deposit = balanceAfterWithdraw - USD(29'000);
5146 ammAlice.
deposit(carol, deposit);
5148 ammAlice.
vote(alice, 0);
5150 auto const tokensNoFee = ammAlice.
withdraw(carol, deposit);
5151 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5154 STAmount(USD, UINT64_C(30'443'43891402717), -11));
5155 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5158 STAmount(USD, UINT64_C(30'443'43891402716), -11));
5162 STAmount(USD, UINT64_C(30'443'43891402713), -11));
5165 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5167 tokensNoFee ==
IOUAmount(746'579'80779913, -8));
5168 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5170 tokensNoFee ==
IOUAmount(746'579'80779912, -8));
5173 tokensNoFee ==
IOUAmount(746'579'80779911, -8));
5174 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
5183 [&](
AMM& ammAlice,
Env& env) {
5189 {USD(1'000), EUR(1'000)},
5192 BEAST_EXPECT(
expectLine(env, alice, EUR(28'990)));
5193 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
5194 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5196 env(pay(carol, alice, EUR(10)),
5202 BEAST_EXPECT(
expectLine(env, alice, EUR(29'000)));
5203 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
5204 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
5207 ammAlice.
vote(alice, 1'000);
5210 env(pay(bob, carol, USD(10)),
5217 env, bob,
STAmount{EUR, UINT64_C(989'8989898989899), -13}));
5219 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5222 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
5225 {{USD(1'000), EUR(1'010)}},
5232 [&](
AMM& ammAlice,
Env& env) {
5234 env(offer(carol, EUR(10), USD(10)));
5236 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
5237 BEAST_EXPECT(
expectLine(env, carol, EUR(30'010)));
5239 env(offer(carol, USD(10), EUR(10)));
5242 ammAlice.
vote(alice, 500);
5244 env(offer(carol, EUR(10), USD(10)));
5251 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
5255 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
5261 STAmount{EUR, UINT64_C(5'025125628140703), -15},
5262 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
5263 if (!features[fixAMMv1_1])
5266 STAmount{USD, UINT64_C(1'004'974874371859), -12},
5267 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5273 STAmount{USD, UINT64_C(1'004'97487437186), -11},
5274 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5278 {{USD(1'000), EUR(1'010)}},
5288 Env env(*
this, features);
5293 {alice, bob, carol, ed},
5295 {USD(2'000), EUR(2'000)});
5296 env(offer(carol, EUR(5), USD(5)));
5297 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
5298 env(pay(bob, ed, USD(10)),
5302 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5303 if (!features[fixAMMv1_1])
5305 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5307 USD(1'000), EUR(1'005), ammAlice.
tokens()));
5312 env, bob,
STAmount(EUR, UINT64_C(1989'999999999999), -12)));
5315 STAmount(EUR, UINT64_C(1005'000000000001), -12),
5324 Env env(*
this, features);
5329 {alice, bob, carol, ed},
5331 {USD(2'000), EUR(2'000)});
5332 env(offer(carol, EUR(5), USD(5)));
5334 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 250);
5335 env(pay(bob, ed, USD(10)),
5339 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5340 if (!features[fixAMMv1_1])
5345 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
5348 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
5356 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
5359 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
5369 Env env(*
this, features);
5374 {alice, bob, carol, ed},
5376 {USD(2'000), EUR(2'000)});
5377 env(offer(carol, EUR(10), USD(10)));
5379 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5380 env(pay(bob, ed, USD(10)),
5384 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5385 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5387 USD(1'005), EUR(1'000), ammAlice.
tokens()));
5396 Env env(*
this, features);
5401 {alice, bob, carol, ed},
5403 {USD(2'000), EUR(2'000)});
5404 env(offer(carol, EUR(9), USD(9)));
5406 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5407 env(pay(bob, ed, USD(10)),
5411 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5413 env, bob,
STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
5416 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
5425 testcase(
"Adjusted Deposit/Withdraw Tokens");
5427 using namespace jtx;
5431 [&](
AMM& ammAlice,
Env& env) {
5439 Account const nataly(
"nataly");
5443 {bob, ed, paul, dan, chris, simon, ben, nataly},
5446 for (
int i = 0; i < 10; ++i)
5450 ammAlice.
deposit(simon, USD(0.1));
5452 ammAlice.
deposit(chris, USD(1));
5454 ammAlice.
deposit(dan, USD(10));
5456 ammAlice.
deposit(bob, USD(100));
5458 ammAlice.
deposit(carol, USD(1'000));
5460 ammAlice.
deposit(ed, USD(10'000));
5462 ammAlice.
deposit(paul, USD(100'000));
5464 ammAlice.
deposit(nataly, USD(1'000'000));
5470 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5473 STAmount{USD, UINT64_C(10'000'0000000013), -10},
5475 else if (features[fixAMMv1_3])
5478 STAmount{USD, UINT64_C(10'000'0000000003), -10},
5483 BEAST_EXPECT(
expectLine(env, ben, USD(1'500'000)));
5484 BEAST_EXPECT(
expectLine(env, simon, USD(1'500'000)));
5485 BEAST_EXPECT(
expectLine(env, chris, USD(1'500'000)));
5486 BEAST_EXPECT(
expectLine(env, dan, USD(1'500'000)));
5487 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5491 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
5492 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5493 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5495 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5496 BEAST_EXPECT(
expectLine(env, ed, USD(1'500'000)));
5497 BEAST_EXPECT(
expectLine(env, paul, USD(1'500'000)));
5498 if (!features[fixAMMv1_1] && !features[fixAMMv1_3])
5502 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
5503 else if (features[fixAMMv1_1] && !features[fixAMMv1_3])
5507 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
5509 BEAST_EXPECT(
expectLine(env, nataly, USD(1'500'000)));
5512 if (!features[fixAMMv1_1])
5516 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
5517 else if (features[fixAMMv1_3])
5521 STAmount{USD, UINT64_C(30'000'0000000003), -10}));
5523 BEAST_EXPECT(
expectLine(env, alice, USD(30'000)));
5529 29950000000 - env.
current()->fees().base.drops()));
5538 [&](
AMM& ammAlice,
Env& env) {
5546 Account const nataly(
"nataly");
5550 {bob, ed, paul, dan, chris, simon, ben, nataly},
5554 for (
int i = 0; i < 10; ++i)
5575 auto const baseFee = env.
current()->fees().base.drops();
5576 if (!features[fixAMMv1_3])
5584 auto const xrpBalance =
5585 (
XRP(2'000'000) -
txfee(env, 20)).getText();
5613 auto const xrpBalance =
5615 auto const xrpBalanceText = xrpBalance.getText();
5625 (xrpBalance +
drops(2)).getText());
5628 (xrpBalance +
drops(3)).getText());
5631 (xrpBalance +
drops(5)).getText());
5646 testcase(
"Auto Delete");
5648 using namespace jtx;
5659 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5660 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5665 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5670 amm.withdrawAll(gw);
5671 BEAST_EXPECT(amm.ammExists());
5696 env(trust(alice,
STAmount{amm.lptIssue(), 10'000}),
5711 amm.expectBalances(
XRP(10'000), USD(10'000), amm.tokens()));
5712 BEAST_EXPECT(amm.expectTradingFee(1'000));
5713 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5717 amm.withdrawAll(alice);
5718 BEAST_EXPECT(!amm.ammExists());
5719 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5730 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5731 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5736 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5740 amm.withdrawAll(gw);
5741 BEAST_EXPECT(amm.ammExists());
5745 BEAST_EXPECT(amm.ammExists());
5747 amm.ammDelete(alice);
5748 BEAST_EXPECT(!amm.ammExists());
5749 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5759 testcase(
"Clawback");
5760 using namespace jtx;
5764 AMM amm(env, gw,
XRP(1'000), USD(1'000));
5772 using namespace jtx;
5773 testAMM([&](
AMM& amm,
Env& env) {
5774 amm.setClose(
false);
5775 auto const info = env.
rpc(
5779 "{\"account\": \"" + to_string(amm.ammAccount()) +
"\"}"));
5783 info[jss::result][jss::account_data][jss::AMMID]
5784 .asString() == to_string(amm.ammID()));
5790 amm.deposit(carol, 1'000);
5791 auto affected = env.
meta()->getJson(
5792 JsonOptions::none)[sfAffectedNodes.fieldName];
5796 for (
auto const& node : affected)
5798 if (node.isMember(sfModifiedNode.fieldName) &&
5799 node[sfModifiedNode.fieldName]
5800 [sfLedgerEntryType.fieldName]
5801 .asString() ==
"AccountRoot" &&
5802 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
5804 .asString() == to_string(amm.ammAccount()))
5806 found = node[sfModifiedNode.fieldName]
5807 [sfFinalFields.fieldName][jss::AMMID]
5808 .asString() == to_string(amm.ammID());
5812 BEAST_EXPECT(found);
5824 testcase(
"Offer/Strand Selection");
5825 using namespace jtx;
5828 auto const ETH = gw1[
"ETH"];
5829 auto const CAN = gw1[
"CAN"];
5835 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5836 fund(env, gw, {alice, carol, bob, ed},
XRP(2'000), {USD(2'000)});
5841 {alice, carol, bob, ed},
5842 {ETH(2'000), CAN(2'000)},
5844 env(rate(gw, gwRate));
5845 env(rate(gw1, gw1Rate));
5849 for (
auto const& rates :
5864 for (
auto i = 0; i < 3; ++i)
5866 Env env(*
this, features);
5867 prep(env, rates.first, rates.second);
5869 if (i == 0 || i == 2)
5875 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5876 env(pay(carol, bob, USD(100)),
5883 BEAST_EXPECT(amm->expectBalances(
5884 USD(1'000), ETH(1'000), amm->tokens()));
5886 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5887 q[i] = Quality(Amounts{
5888 ETH(2'000) - env.
balance(carol, ETH),
5889 env.
balance(bob, USD) - USD(2'000)});
5892 BEAST_EXPECT(q[0] > q[1]);
5894 BEAST_EXPECT(q[0] == q[2]);
5901 for (
auto i = 0; i < 3; ++i)
5903 Env env(*
this, features);
5904 prep(env, rates.first, rates.second);
5906 if (i == 0 || i == 2)
5912 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5913 env(offer(alice, USD(400), ETH(400)));
5918 BEAST_EXPECT(amm->expectBalances(
5919 USD(1'000), ETH(1'000), amm->tokens()));
5921 if (i == 0 || i == 2)
5930 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
5941 for (
auto i = 0; i < 3; ++i)
5943 Env env(*
this, features);
5944 prep(env, rates.first, rates.second);
5946 if (i == 0 || i == 2)
5952 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5953 env(pay(carol, bob, USD(100)),
5960 BEAST_EXPECT(!amm->expectBalances(
5961 USD(1'000), ETH(1'000), amm->tokens()));
5963 if (i == 2 && !features[fixAMMv1_1])
5965 if (rates.first == 1.5)
5967 if (!features[fixAMMv1_1])
5975 UINT64_C(378'6327949540823),
5979 UINT64_C(283'9745962155617),
5989 UINT64_C(378'6327949540813),
5993 UINT64_C(283'974596215561),
5998 if (!features[fixAMMv1_1])
6006 UINT64_C(325'299461620749),
6010 UINT64_C(243'9745962155617),
6020 UINT64_C(325'299461620748),
6024 UINT64_C(243'974596215561),
6030 if (rates.first == 1.5)
6038 ETH, UINT64_C(378'6327949540812), -13},
6041 UINT64_C(283'9745962155609),
6052 ETH, UINT64_C(325'2994616207479), -13},
6055 UINT64_C(243'9745962155609),
6059 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
6060 q[i] = Quality(Amounts{
6061 ETH(2'000) - env.
balance(carol, ETH),
6062 env.
balance(bob, USD) - USD(2'000)});
6065 BEAST_EXPECT(q[1] > q[0]);
6067 BEAST_EXPECT(q[2] > q[1]);
6071 for (
auto i = 0; i < 3; ++i)
6073 Env env(*
this, features);
6074 prep(env, rates.first, rates.second);
6076 if (i == 0 || i == 2)
6082 amm.emplace(env, ed, USD(1'000), ETH(1'000));
6083 env(offer(alice, USD(250), ETH(400)));
6088 BEAST_EXPECT(!amm->expectBalances(
6089 USD(1'000), ETH(1'000), amm->tokens()));
6095 if (rates.first == 1.5)
6097 if (!features[fixAMMv1_1])
6100 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
6107 USD, UINT64_C(40'5694150420947), -13},
6109 ETH, UINT64_C(64'91106406735152), -14},
6123 ETH, UINT64_C(335'0889359326475), -13},
6125 USD, UINT64_C(209'4305849579047), -13},
6132 if (!features[fixAMMv1_1])
6141 ETH, UINT64_C(335'0889359326485), -13},
6143 USD, UINT64_C(209'4305849579053), -13},
6156 ETH, UINT64_C(335'0889359326475), -13},
6158 USD, UINT64_C(209'4305849579047), -13},
6184 for (
auto i = 0; i < 3; ++i)
6186 Env env(*
this, features);
6187 prep(env, rates.first, rates.second);
6190 if (i == 0 || i == 2)
6198 amm.emplace(env, ed, ETH(1'000), USD(1'000));
6200 env(pay(carol, bob, USD(100)),
6206 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
6208 if (i == 2 && !features[fixAMMv1_1])
6210 if (rates.first == 1.5)
6213 BEAST_EXPECT(amm->expectBalances(
6214 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
6220 BEAST_EXPECT(amm->expectBalances(
6222 ETH, UINT64_C(1'179'540094339627), -12},
6223 STAmount{USD, UINT64_C(847'7880529867501), -13},
6232 UINT64_C(343'3179205198749),
6236 UINT64_C(343'3179205198749),
6242 UINT64_C(362'2119470132499),
6246 UINT64_C(362'2119470132499),
6253 if (rates.first == 1.5)
6256 BEAST_EXPECT(amm->expectBalances(
6258 ETH, UINT64_C(1'176'660389557593), -12},
6264 BEAST_EXPECT(amm->expectBalances(
6265 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
6266 STAmount{USD, UINT64_C(847'7880529867501), -13},
6275 UINT64_C(343'3179205198749),
6279 UINT64_C(343'3179205198749),
6285 UINT64_C(362'2119470132499),
6289 UINT64_C(362'2119470132499),
6294 q[i] = Quality(Amounts{
6295 ETH(2'000) - env.
balance(carol, ETH),
6296 env.
balance(bob, USD) - USD(2'000)});
6298 BEAST_EXPECT(q[1] > q[0]);
6299 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
6307 testcase(
"Fix Default Inner Object");
6308 using namespace jtx;
6319 Env env(*
this, features);
6320 fund(env, gw, {alice},
XRP(1'000), {USD(10)});
6326 {.tfee = tfee, .close = closeLedger});
6327 amm.deposit(alice, USD(10),
XRP(10));
6330 .
account = gw, .asset1Out = USD(1), .err =
ter(err2)});
6338 .
account = gw, .asset1Out = USD(2), .err =
ter(err4)});
6345 all - fixInnerObjTemplate,
6360 all - fixInnerObjTemplate,
6372 all - fixInnerObjTemplate,
6381 all - fixInnerObjTemplate,
6395 all - fixInnerObjTemplate,
6407 testcase(
"Fix changeSpotPriceQuality");
6408 using namespace jtx;
6413 SucceedShouldSucceedResize,
6425 auto const xrpIouAmounts10_100 =
6427 auto const iouXrpAmounts10_100 =
6432 {
"0.001519763260828713",
"1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
6433 {
"0.01099814367603737",
"1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
6434 {
"0.78",
"796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
6435 {
"105439.2955578965",
"49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
6436 {
"12408293.23445213",
"4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
6437 {
"1892611",
"0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
6438 {
"423028.8508101858",
"3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
6439 {
"44565388.41001027",
"73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
6440 {
"66831.68494832662",
"16", Quality{6346111134641742975}, 0, FailShouldSucceed},
6441 {
"675.9287302203422",
"1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
6442 {
"7047.112186735699",
"1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
6443 {
"840236.4402981238",
"47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
6444 {
"992715.618909774",
"189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
6445 {
"504636667521",
"185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
6446 {
"992706.7218636649",
"189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
6447 {
"1.068737911388205",
"127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
6448 {
"17932506.56880419",
"189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
6449 {
"1.066379294658174",
"128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
6450 {
"350131413924",
"1576879.110907892", Quality{6487411636539049449}, 650,
Fail},
6451 {
"422093460",
"2.731797662057464", Quality{6702911108534394924}, 1000,
Fail},
6452 {
"76128132223",
"367172.7148422662", Quality{6487263463413514240}, 548,
Fail},
6453 {
"132701839250",
"280703770.7695443", Quality{6273750681188885075}, 562,
Fail},
6454 {
"994165.7604612011",
"189551302411", Quality{5697835592690668727}, 815,
Fail},
6455 {
"45053.33303227917",
"86612695359", Quality{5625695218943638190}, 500,
Fail},
6456 {
"199649.077043865",
"14017933007", Quality{5766034667318524880}, 324,
Fail},
6457 {
"27751824831.70903",
"78896950", Quality{6272538159621630432}, 500,
Fail},
6458 {
"225.3731275781907",
"156431793648", Quality{5477818047604078924}, 989,
Fail},
6459 {
"199649.077043865",
"14017933007", Quality{5766036094462806309}, 324,
Fail},
6460 {
"3.590272027140361",
"20677643641", Quality{5406056147042156356}, 808,
Fail},
6461 {
"1.070884664490231",
"127604712776", Quality{5268620608623825741}, 293,
Fail},
6462 {
"3272.448829820197",
"6275124076", Quality{5625710328924117902}, 81,
Fail},
6463 {
"0.009059512633902926",
"7994028", Quality{5477511954775533172}, 1000,
Fail},
6464 {
"1",
"1.0", Quality{0}, 100,
Fail},
6465 {
"1.0",
"1", Quality{0}, 100,
Fail},
6466 {
"10",
"10.0", Quality{xrpIouAmounts10_100}, 100,
Fail},
6467 {
"10.0",
"10", Quality{iouXrpAmounts10_100}, 100,
Fail},
6468 {
"69864389131",
"287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
6469 {
"4328342973",
"12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
6470 {
"32347017",
"7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
6471 {
"61697206161",
"36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
6472 {
"1654524979",
"7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
6473 {
"88621.22277293179",
"5128418948", Quality{5766347291552869205}, 380, Succeed},
6474 {
"1892611",
"0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
6475 {
"4542.639373338766",
"24554809", Quality{5838994982188783710}, 0, Succeed},
6476 {
"5132932546",
"88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
6477 {
"78929964.1549083",
"1506494795", Quality{5986890029845558688}, 589, Succeed},
6478 {
"10096561906",
"44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
6479 {
"5092.219565514988",
"8768257694", Quality{5626349534958379008}, 503, Succeed},
6480 {
"1819778294",
"8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
6481 {
"6970462.633911943",
"57359281", Quality{6054087899185946624}, 850, Succeed},
6482 {
"3983448845",
"2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
6486 {
"771493171",
"1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
6490 boost::regex rx(
"^\\d+$");
6491 boost::smatch match;
6494 Env env(*
this, features, std::make_unique<CaptureLogs>(&logs));
6495 auto rules = env.
current()->rules();
6497 for (
auto const& t : tests)
6504 auto const& quality = std::get<Quality>(t);
6505 auto const tfee = std::get<std::uint16_t>(t);
6506 auto const status = std::get<Status>(t);
6507 auto const poolInIsXRP =
6508 boost::regex_search(std::get<0>(t), match, rx);
6509 auto const poolOutIsXRP =
6510 boost::regex_search(std::get<1>(t), match, rx);
6511 assert(!(poolInIsXRP && poolOutIsXRP));
6512 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
6513 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
6517 Amounts{poolIn, poolOut},
6524 if (status == SucceedShouldSucceedResize)
6526 if (!features[fixAMMv1_1])
6527 BEAST_EXPECT(Quality{*amounts} < quality);
6529 BEAST_EXPECT(Quality{*amounts} >= quality);
6531 else if (status == Succeed)
6533 if (!features[fixAMMv1_1])
6535 Quality{*amounts} >= quality ||
6537 Quality{*amounts}, quality,
Number{1, -7}));
6539 BEAST_EXPECT(Quality{*amounts} >= quality);
6541 else if (status == FailShouldSucceed)
6544 features[fixAMMv1_1] &&
6545 Quality{*amounts} >= quality);
6547 else if (status == SucceedShouldFail)
6550 !features[fixAMMv1_1] &&
6551 Quality{*amounts} < quality &&
6553 Quality{*amounts}, quality,
Number{1, -7}));
6562 if (status ==
Fail && quality != Quality{0})
6564 auto tinyOffer = [&]() {
6571 Amounts{poolIn, poolOut},
6575 else if (
isXRP(poolOut))
6580 Amounts{poolIn, poolOut},
6585 auto const takerPays = toAmount<STAmount>(
6590 Amounts{poolIn, poolOut}, takerPays, tfee)};
6592 BEAST_EXPECT(Quality(tinyOffer) < quality);
6594 else if (status == FailShouldSucceed)
6596 BEAST_EXPECT(!features[fixAMMv1_1]);
6598 else if (status == SucceedShouldFail)
6600 BEAST_EXPECT(features[fixAMMv1_1]);
6607 !strcmp(e.
what(),
"changeSpotPriceQuality failed"));
6609 !features[fixAMMv1_1] && status == FailShouldSucceed);
6618 BEAST_EXPECT(!res.has_value());
6625 using namespace jtx;
6627 testAMM([&](
AMM& ammAlice,
Env& env) {
6635 testAMM([&](
AMM& ammAlice,
Env& env) {
6643 testAMM([&](
AMM& ammAlice,
Env& env) {
6651 testAMM([&](
AMM& ammAlice,
Env& env) {
6654 .asset2Out =
XRP(100),
6660 testAMM([&](
AMM& ammAlice,
Env& env) {
6663 .asset2Out = BAD(100),
6669 testAMM([&](
AMM& ammAlice,
Env& env) {
6671 jv[jss::TransactionType] = jss::AMMWithdraw;
6673 jv[jss::Account] = alice.human();
6675 XRP(100).value().setJson(jv[jss::Amount]);
6676 USD(100).value().setJson(jv[jss::EPrice]);
6684 using namespace jtx;
6690 Account const gatehub{
"gatehub"};
6691 Account const bitstamp{
"bitstamp"};
6692 Account const trader{
"trader"};
6693 auto const usdGH = gatehub[
"USD"];
6694 auto const btcGH = gatehub[
"BTC"];
6695 auto const usdBIT = bitstamp[
"USD"];
6699 char const* testCase;
6700 double const poolUsdBIT;
6701 double const poolUsdGH;
6714 double const offer1BtcGH = 0.1;
6715 double const offer2BtcGH = 0.1;
6716 double const offer2UsdGH = 1;
6717 double const rateBIT = 0.0;
6718 double const rateGH = 0.0;
6723 for (
auto const& input : {
6725 .testCase =
"Test Fix Overflow Offer",
6728 .sendMaxUsdBIT{usdBIT(50)},
6729 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6732 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6733 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6734 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6735 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6736 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6737 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6738 .lpTokenBalance = {28'61817604250837, -14},
6739 .lpTokenBalanceAlt =
IOUAmount{28'61817604250836, -14},
6747 .testCase =
"Overflow test {1, 100, 0.111}",
6750 .sendMaxUsdBIT{usdBIT(0.111)},
6751 .sendUsdGH{usdGH, 100},
6754 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6755 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6756 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6757 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6758 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6759 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6760 .lpTokenBalance{10, 0},
6761 .offer1BtcGH = 1e-5,
6763 .offer2UsdGH = 1e-5,
6768 .testCase =
"Overflow test {1, 100, 1.00}",
6771 .sendMaxUsdBIT{usdBIT(1.00)},
6772 .sendUsdGH{usdGH, 100},
6775 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6776 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6777 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6778 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6779 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6780 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6781 .lpTokenBalance{10, 0},
6782 .offer1BtcGH = 1e-5,
6784 .offer2UsdGH = 1e-5,
6789 .testCase =
"Overflow test {1, 100, 4.6432}",
6792 .sendMaxUsdBIT{usdBIT(4.6432)},
6793 .sendUsdGH{usdGH, 100},
6796 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6797 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6798 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6799 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6800 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6801 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6802 .lpTokenBalance{10, 0},
6803 .offer1BtcGH = 1e-5,
6805 .offer2UsdGH = 1e-5,
6810 .testCase =
"Overflow test {1, 100, 10}",
6813 .sendMaxUsdBIT{usdBIT(10)},
6814 .sendUsdGH{usdGH, 100},
6817 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6818 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6819 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6820 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6821 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6822 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6823 .lpTokenBalance{10, 0},
6824 .offer1BtcGH = 1e-5,
6826 .offer2UsdGH = 1e-5,
6831 .testCase =
"Overflow test {50, 100, 5.55}",
6834 .sendMaxUsdBIT{usdBIT(5.55)},
6835 .sendUsdGH{usdGH, 100},
6838 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6839 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6840 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6841 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6842 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6843 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6844 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6845 .offer1BtcGH = 1e-5,
6847 .offer2UsdGH = 1e-5,
6852 .testCase =
"Overflow test {50, 100, 50.00}",
6855 .sendMaxUsdBIT{usdBIT(50.00)},
6856 .sendUsdGH{usdGH, 100},
6857 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6858 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6859 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6860 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6861 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6862 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6863 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6864 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6865 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6866 .offer1BtcGH = 1e-5,
6868 .offer2UsdGH = 1e-5,
6873 .testCase =
"Overflow test {50, 100, 232.16}",
6876 .sendMaxUsdBIT{usdBIT(232.16)},
6877 .sendUsdGH{usdGH, 100},
6880 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6881 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6882 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6883 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6884 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6885 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6886 .lpTokenBalance{70'71067811865475, -14},
6887 .offer1BtcGH = 1e-5,
6889 .offer2UsdGH = 1e-5,
6894 .testCase =
"Overflow test {50, 100, 500}",
6897 .sendMaxUsdBIT{usdBIT(500)},
6898 .sendUsdGH{usdGH, 100},
6901 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6902 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6903 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6904 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6905 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6906 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6907 .lpTokenBalance{70'71067811865475, -14},
6908 .offer1BtcGH = 1e-5,
6910 .offer2UsdGH = 1e-5,
6916 testcase(input.testCase);
6917 for (
auto const& features :
6918 {all - fixAMMOverflowOffer - fixAMMv1_1 - fixAMMv1_3, all})
6920 Env env(*
this, features, std::make_unique<CaptureLogs>(&logs));
6922 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6925 if (input.rateGH != 0.0)
6926 env(rate(gatehub, input.rateGH));
6927 if (input.rateBIT != 0.0)
6928 env(rate(bitstamp, input.rateBIT));
6930 env(trust(trader, usdGH(10'000'000)));
6931 env(trust(trader, usdBIT(10'000'000)));
6932 env(trust(trader, btcGH(10'000'000)));
6935 env(pay(gatehub, trader, usdGH(100'000)));
6936 env(pay(gatehub, trader, btcGH(100'000)));
6937 env(pay(bitstamp, trader, usdBIT(100'000)));
6943 usdGH(input.poolUsdGH),
6944 usdBIT(input.poolUsdBIT)};
6948 amm.getLPTokensBalance();
6950 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6953 btcGH(input.offer2BtcGH),
6954 usdGH(input.offer2UsdGH)));
6957 env(pay(trader, trader, input.sendUsdGH),
6959 path(~btcGH, ~usdGH),
6964 auto const failUsdGH =
6965 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6966 auto const failUsdBIT =
6967 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6968 auto const goodUsdGH =
6969 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6970 auto const goodUsdBIT =
6971 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6972 auto const lpTokenBalance =
6973 env.
enabled(fixAMMv1_3) && input.lpTokenBalanceAlt
6974 ? *input.lpTokenBalanceAlt
6975 : input.lpTokenBalance;
6976 if (!features[fixAMMOverflowOffer])
6978 BEAST_EXPECT(amm.expectBalances(
6979 failUsdGH, failUsdBIT, lpTokenBalance));
6983 BEAST_EXPECT(amm.expectBalances(
6984 goodUsdGH, goodUsdBIT, lpTokenBalance));
6989 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6993 Number const sqrtPoolProduct =
6994 root2(goodUsdGH * goodUsdBIT);
7005 (sqrtPoolProduct +
Number{1, -14} >=
7006 input.lpTokenBalance));
7015 testcase(
"swapRounding");
7016 using namespace jtx;
7018 STAmount const xrpPool{
XRP, UINT64_C(51600'000981)};
7019 STAmount const iouPool{USD, UINT64_C(803040'9987141784), -10};
7021 STAmount const xrpBob{
XRP, UINT64_C(1092'878933)};
7023 USD, UINT64_C(3'988035892323031), -28};
7026 [&](
AMM& amm,
Env& env) {
7028 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(
XRP, USD);
7031 env.
fund(xrpBob, bob);
7032 env.
trust(USD(1'000'000), bob);
7033 env(pay(gw, bob, iouBob));
7036 env(offer(bob,
XRP(6300), USD(100'000)));
7041 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
7043 {{xrpPool, iouPool}},
7046 {jtx::supported_amendments() | fixAMMv1_1});
7052 testcase(
"AMM Offer Blocked By LOB");
7053 using namespace jtx;
7059 Env env(*
this, features);
7061 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
7063 env(offer(alice,
XRP(1), USD(0.01)));
7066 AMM amm(env, gw,
XRP(200'000), USD(100'000));
7070 env(offer(carol, USD(0.49),
XRP(1)));
7073 if (!features[fixAMMv1_1])
7075 BEAST_EXPECT(amm.expectBalances(
7076 XRP(200'000), USD(100'000), amm.tokens()));
7078 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
7081 env, carol, 1, {{Amounts{USD(0.49),
XRP(1)}}}));
7085 BEAST_EXPECT(amm.expectBalances(
7086 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
7088 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
7097 Env env(*
this, features);
7099 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
7103 AMM amm(env, gw,
XRP(200'000), USD(100'000));
7106 env(offer(carol, USD(0.49),
XRP(1)));
7110 BEAST_EXPECT(amm.expectBalances(
7111 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
7118 Env env(*
this, features);
7119 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
7123 env(offer(bob, USD(1),
XRPAmount(500)));
7125 AMM amm(env, alice,
XRP(1'000), USD(500));
7126 env(offer(carol,
XRP(100), USD(55)));
7128 if (!features[fixAMMv1_1])
7131 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
7133 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
7135 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
7139 BEAST_EXPECT(amm.expectBalances(
7141 STAmount{USD, UINT64_C(550'000000055), -9},
7149 STAmount{USD, 4'99999995, -8}}}}));
7151 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
7158 Env env(*
this, features);
7159 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
7161 AMM amm(env, alice,
XRP(1'000), USD(500));
7162 env(offer(carol,
XRP(100), USD(55)));
7164 BEAST_EXPECT(amm.expectBalances(
7166 STAmount{USD, UINT64_C(550'000000055), -9},
7180 testcase(
"LPToken Balance");
7181 using namespace jtx;
7186 Env env(*
this, features, std::make_unique<CaptureLogs>(&logs));
7192 {USD(1'000'000'000)});
7193 AMM amm(env, gw,
XRP(2), USD(1));
7194 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
7195 amm.deposit(carol,
IOUAmount{1'000'000});
7196 amm.withdrawAll(alice);
7197 BEAST_EXPECT(amm.expectLPTokens(alice,
IOUAmount{0}));
7198 amm.withdrawAll(carol);
7199 BEAST_EXPECT(amm.expectLPTokens(carol,
IOUAmount{0}));
7201 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
7202 auto const lpTokenBalance =
7203 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
7205 lpToken ==
"1414.213562373095" &&
7206 lpTokenBalance ==
"1414.213562373");
7207 if (!features[fixAMMv1_1])
7210 BEAST_EXPECT(amm.ammExists());
7214 amm.withdrawAll(gw);
7215 BEAST_EXPECT(!amm.ammExists());
7221 for (
auto const& lp : {gw, bob})
7223 Env env(*
this, features);
7224 auto const ABC = gw[
"ABC"];
7228 {alice, carol, bob},
7230 {USD(1'000'000'000), ABC(1'000'000'000'000)});
7231 AMM amm(env, lp, ABC(2'000'000), USD(1));
7232 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
7233 amm.deposit(carol,
IOUAmount{1'000'000});
7234 amm.withdrawAll(alice);
7235 amm.withdrawAll(carol);
7237 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
7238 auto const lpTokenBalance =
7239 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
7241 lpToken ==
"1414.213562373095" &&
7242 lpTokenBalance ==
"1414.213562373");
7243 if (!features[fixAMMv1_1])
7246 BEAST_EXPECT(amm.ammExists());
7250 amm.withdrawAll(lp);
7251 BEAST_EXPECT(!amm.ammExists());
7258 Env env(*
this, features);
7259 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)});
7260 AMM amm(env, gw,
XRP(10), USD(10));
7261 amm.deposit(alice, 1'000);
7264 BEAST_EXPECT(res && !res.value());
7267 BEAST_EXPECT(res && !res.value());
7271 Env env(*
this, features);
7272 fund(env, gw, {alice},
XRP(1'000), {USD(1'000), EUR(1'000)});
7273 AMM amm(env, gw, EUR(10), USD(10));
7274 amm.deposit(alice, 1'000);
7277 BEAST_EXPECT(res && !res.value());
7280 BEAST_EXPECT(res && !res.value());
7284 Env env(*
this, features);
7286 auto const YAN = gw1[
"YAN"];
7287 fund(env, gw, {gw1},
XRP(1'000), {USD(1'000)});
7288 fund(env, gw1, {gw},
XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
7289 AMM amm(env, gw1, YAN(10), USD(10));
7290 amm.deposit(gw, 1'000);
7293 BEAST_EXPECT(res && !res.value());
7295 BEAST_EXPECT(res && !res.value());
7302 testcase(
"test clawback from AMM account");
7303 using namespace jtx;
7306 Env env(*
this, features);
7309 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7314 if (!features[featureAMMClawback])
7340 Issue usd(USD.issue().currency, amm.ammAccount());
7349 testcase(
"test AMMDeposit with frozen assets");
7350 using namespace jtx;
7357 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7367 Env env(*
this, features);
7368 testAMMDeposit(env, [&](
AMM& amm) {
7382 Env env(*
this, features);
7383 testAMMDeposit(env, [&](
AMM& amm) {
7394 if (features[featureAMMClawback])
7399 Env env(*
this, features);
7400 testAMMDeposit(env, [&](
AMM& amm) {
7415 Env env(*
this, features);
7416 testAMMDeposit(env, [&](
AMM& amm) {
7431 testcase(
"Fix Reserve Check On Withdrawal");
7432 using namespace jtx;
7437 auto test = [&](
auto&& cb) {
7438 Env env(*
this, features);
7439 auto const starting_xrp =
7440 reserve(env, 2) + env.
current()->fees().base * 5;
7441 env.
fund(starting_xrp, gw);
7442 env.
fund(starting_xrp, alice);
7443 env.
trust(USD(2'000), alice);
7445 env(pay(gw, alice, USD(2'000)));
7447 AMM amm(env, gw, EUR(1'000), USD(1'000));
7448 amm.deposit(alice, USD(1));
7453 test([&](
AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
7456 test([&](
AMM& amm) {
7459 .asset1Out = EUR(0.1),
7460 .asset2Out = USD(0.1),
7464 .asset1Out = USD(0.1),
7465 .asset2Out = EUR(0.1),
7470 test([&](
AMM& amm) {
7472 .
account = alice, .asset1Out = EUR(0.1), .err = err});
7480 using namespace test::jtx;
7483 testcase(
"Failed pseudo-account allocation " + suffix);
7485 Env env{*
this, features, std::make_unique<CaptureLogs>(&logs)};
7486 env.fund(
XRP(30'000), gw, alice);
7488 env(trust(alice, gw[
"USD"](30'000), 0));
7489 env(pay(gw, alice, USD(10'000)));
7494 auto const keylet = keylet::amm(amount.
issue(), amount2.
issue());
7495 for (
int i = 0; i < 256; ++i)
7500 env(pay(env.master.id(), accountId,
XRP(1000)),
7518 "terADDRESS_COLLISION",
7525 testcase(
"Deposit and Withdraw Rounding V2");
7526 using namespace jtx;
7528 auto const XPM = gw[
"XPM"];
7530 STAmount xpmBalance{XPM, UINT64_C(18'610'359'80246901), -8};
7531 STAmount amount{XPM, UINT64_C(6'566'496939465400), -12};
7535 Env env(*
this, features);
7538 env(trust(alice, XPM(7'000)));
7539 env(pay(gw, alice, amount));
7544 amm.lptIssue(), UINT64_C(3'234'987'266'485968), -6};
7546 IOUAmount{amm.getLPTokensBalance() - lptAMMBalance};
7548 env(amm.bid(
BidArg{.account = gw, .bidMin = burn, .bidMax = burn}));
7552 [&](
AMM& amm,
Env& env) {
7556 .
account = alice, .asset1In = amount, .err = err});
7560 [&](
AMM& amm,
Env& env) {
7561 auto const [amount, amount2, lptAMM] = amm.balances(
XRP, XPM);
7562 auto const withdraw =
STAmount{XPM, 1, -5};
7564 auto const [amount_, amount2_, lptAMM_] =
7565 amm.balances(
XRP, XPM);
7567 BEAST_EXPECT((amount2 - amount2_) > withdraw);
7569 BEAST_EXPECT((amount2 - amount2_) <= withdraw);
7581 auto const [amount, amount2, lptBalance] = amm.balances(GBP, EUR);
7584 env.
enabled(fixAMMv1_3) ? Number::upward : Number::getround());
7585 auto const res =
root2(amount * amount2);
7588 BEAST_EXPECT(res < lptBalance);
7590 BEAST_EXPECT(res >= lptBalance);
7596 testcase(
"Deposit Rounding");
7597 using namespace jtx;
7600 for (
auto const& deposit :
7609 [&](
AMM& ammAlice,
Env& env) {
7615 {GBP(100'000), EUR(100'000)},
7628 {{GBP(30'000), EUR(30'000)}},
7636 [&](
AMM& ammAlice,
Env& env) {
7642 {GBP(100'000), EUR(100'000)},
7647 EUR, UINT64_C(10'1234567890123456), -16};
7649 GBP, UINT64_C(10'1234567890123456), -16};
7653 .asset1In = depositEuro,
7654 .asset2In = depositGBP});
7655 invariant(ammAlice, env,
"dep2",
false);
7657 {{GBP(30'000), EUR(30'000)}},
7663 for (
auto const& exponent : {1, 2, 3, 4, -3 , -6, -9})
7666 [&](
AMM& ammAlice,
Env& env) {
7672 {GBP(100'000), EUR(100'000)},
7676 STAmount const depositEuro{EUR, 1, exponent};
7677 STAmount const depositGBP{GBP, 1, exponent};
7681 .asset1In = depositEuro,
7682 .asset2In = depositGBP});
7687 exponent != -3 && !env.
enabled(fixAMMv1_3));
7689 {{GBP(10'000), EUR(30'000)}},
7697 [&](
AMM& ammAlice,
Env& env) {
7703 {GBP(100'000), EUR(100'000)},
7709 .tokens =
IOUAmount{10'1234567890123456, -16}});
7710 invariant(ammAlice, env,
"dep4",
false);
7712 {{GBP(7'000), EUR(30'000)}},
7718 for (
auto const& tokens :
7729 [&](
AMM& ammAlice,
Env& env) {
7735 {GBP(100'000), EUR(1'000'000)},
7743 invariant(ammAlice, env,
"dep5",
false);
7745 {{GBP(7'000), EUR(30'000)}},
7754 [&](
AMM& ammAlice,
Env& env) {
7760 {GBP(100'000), EUR(100'000)},
7765 bob, GBP(1'000), std::nullopt,
STAmount{GBP, 5});
7766 invariant(ammAlice, env,
"dep6",
false);
7768 {{GBP(30'000), EUR(30'000)}},
7777 testcase(
"Withdraw Rounding");
7779 using namespace jtx;
7783 [&](
AMM& ammAlice,
Env& env) {
7785 invariant(ammAlice, env,
"with1",
false);
7787 {{GBP(7'000), EUR(30'000)}},
7794 [&](
AMM& ammAlice,
Env& env) {
7797 invariant(ammAlice, env,
"with2",
false);
7799 {{GBP(7'000), EUR(30'000)}},
7806 [&](
AMM& ammAlice,
Env& env) {
7810 .asset2Out =
STAmount{EUR, 15'000},
7812 invariant(ammAlice, env,
"with3",
false);
7814 {{GBP(7'000), EUR(30'000)}},
7825 [&](
AMM& ammAlice,
Env& env) {
7830 invariant(ammAlice, env,
"with4",
false);
7832 {{GBP(7'000), EUR(30'000)}},
7839 [&](
AMM& ammAlice,
Env& env) {
7845 {GBP(100'000), EUR(100'000)},
7856 invariant(ammAlice, env,
"with5",
false);
7858 {{GBP(7'000), EUR(30'000)}},
7865 [&](
AMM& ammAlice,
Env& env) {
7871 invariant(ammAlice, env,
"with6",
false);
7873 {{GBP(7'000), EUR(30'000)}},
7880 [&](
AMM& ammAlice,
Env& env) {
7886 invariant(ammAlice, env,
"with7",
true);
7888 {{GBP(7'000), EUR(30'000)}},
7898 testInvalidInstance();
7899 testInstanceCreate();
7900 testInvalidDeposit(all);
7901 testInvalidDeposit(all - featureAMMClawback);
7903 testInvalidWithdraw();
7905 testInvalidFeeVote();
7909 testBid(all - fixAMMv1_3);
7910 testBid(all - fixAMMv1_1 - fixAMMv1_3);
7911 testInvalidAMMPayment();
7912 testBasicPaymentEngine(all);
7913 testBasicPaymentEngine(all - fixAMMv1_1 - fixAMMv1_3);
7914 testBasicPaymentEngine(all - fixReducedOffersV2);
7915 testBasicPaymentEngine(
7916 all - fixAMMv1_1 - fixAMMv1_3 - fixReducedOffersV2);
7921 testAMMAndCLOB(all);
7922 testAMMAndCLOB(all - fixAMMv1_1 - fixAMMv1_3);
7923 testTradingFee(all);
7924 testTradingFee(all - fixAMMv1_3);
7925 testTradingFee(all - fixAMMv1_1 - fixAMMv1_3);
7926 testAdjustedTokens(all);
7927 testAdjustedTokens(all - fixAMMv1_3);
7928 testAdjustedTokens(all - fixAMMv1_1 - fixAMMv1_3);
7933 testSelection(all - fixAMMv1_1 - fixAMMv1_3);
7934 testFixDefaultInnerObj();
7936 testFixOverflowOffer(all);
7937 testFixOverflowOffer(all - fixAMMv1_3);
7938 testFixOverflowOffer(all - fixAMMv1_1 - fixAMMv1_3);
7940 testFixChangeSpotPriceQuality(all);
7941 testFixChangeSpotPriceQuality(all - fixAMMv1_1 - fixAMMv1_3);
7942 testFixAMMOfferBlockedByLOB(all);
7943 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1 - fixAMMv1_3);
7944 testLPTokenBalance(all);
7945 testLPTokenBalance(all - fixAMMv1_3);
7946 testLPTokenBalance(all - fixAMMv1_1 - fixAMMv1_3);
7947 testAMMClawback(all);
7948 testAMMClawback(all - featureAMMClawback);
7949 testAMMClawback(all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback);
7950 testAMMDepositWithFrozenAssets(all);
7951 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
7952 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
7953 testAMMDepositWithFrozenAssets(
7954 all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback);
7955 testFixReserveCheckOnWithdrawal(all);
7956 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
7957 testDepositAndWithdrawRounding(all);
7958 testDepositAndWithdrawRounding(all - fixAMMv1_3);
7959 testDepositRounding(all);
7960 testDepositRounding(all - fixAMMv1_3);
7961 testWithdrawRounding(all);
7962 testWithdrawRounding(all - fixAMMv1_3);
7963 testFailedPseudoAccount();
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
Issue const & issue() const
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.
bool enabled(uint256 feature) const
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 regular signature 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.
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.
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
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
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
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
std::uint32_t constexpr AUCTION_SLOT_INTERVAL_DURATION
constexpr std::uint32_t tfLimitQuality
constexpr std::uint32_t tfTwoAssetIfEmpty
STAmount amountFromString(Asset const &asset, std::string const &amount)
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
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 testFixOverflowOffer(FeatureBitset featuresInitial)
void testInvalidDeposit(FeatureBitset features)
void testDepositRounding(FeatureBitset all)
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 testInvalidWithdraw()
void testAMMAndCLOB(FeatureBitset features)
void testInvalidInstance()
void testDepositAndWithdrawRounding(FeatureBitset features)
void testFailedPseudoAccount()
void invariant(jtx::AMM &amm, jtx::Env &env, std::string const &msg, bool shouldFail)
void testBasicPaymentEngine(FeatureBitset features)
void testWithdrawRounding(FeatureBitset all)
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< LPToken > tokens
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.