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/tx/detail/AMMBid.h>
32#include <xrpl/basics/Number.h>
33#include <xrpl/protocol/AMMCore.h>
34#include <xrpl/protocol/Feature.h>
35#include <xrpl/protocol/TER.h>
37#include <boost/regex.hpp>
87 {{
USD(20'000),
BTC(0.5)}});
138 BEAST_EXPECT(amm.expectTradingFee(1'000));
139 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
166 BEAST_EXPECT(!ammAlice.ammExists());
175 BEAST_EXPECT(!ammAlice.ammExists());
183 BEAST_EXPECT(!ammAlice.ammExists());
200 BEAST_EXPECT(!ammAlice.ammExists());
209 BEAST_EXPECT(!ammAlice.ammExists());
218 BEAST_EXPECT(!ammAlice.ammExists());
237 BEAST_EXPECT(!ammAlice.ammExists());
262 BEAST_EXPECT(!ammAlice.ammExists());
327 auto const starting_xrp =
329 env.
fund(starting_xrp,
gw);
344 auto const starting_xrp =
346 env.
fund(starting_xrp,
gw);
401 auto const token1 = ammAlice.
lptIssue();
402 auto const token2 = ammAlice1.lptIssue();
427 auto const USD1 = gw1[
"USD"];
624 for (
auto const& it : invalidOptions)
643 jv[jss::TransactionType] = jss::AMMDeposit;
665 jv[jss::TransactionType] = jss::AMMDeposit;
670 jv[jss::LPTokenOut] =
873 [&](
AMM& ammAlice,
Env& env) {
875 if (!features[featureAMMClawback])
919 [&](
AMM& ammAlice,
Env& env) {
922 if (!features[featureAMMClawback])
981 [&](
AMM& ammAlice,
Env& env) {
1021 {{
USD(20'000),
BTC(0.5)}});
1025 Env env(*
this, features);
1040 if (features[featureAMMClawback])
1129 auto const starting_xrp =
1164 auto const starting_xrp =
1326 using namespace jtx;
1330 auto const baseFee = env.
current()->fees().base;
1344 for (
Number const deltaLPTokens :
1345 {
Number{UINT64_C(100000'0000000009), -10},
1346 Number{UINT64_C(100000'0000000001), -10}})
1352 deltaLPTokens.
mantissa(), deltaLPTokens.exponent()};
1365 BEAST_EXPECT((finalLPToken - initLPToken ==
IOUAmount{1, 5}));
1366 BEAST_EXPECT(finalLPToken - initLPToken < deltaLPTokens);
1370 Number const fr = deltaLPTokens / 1e7;
1374 Number const deltaXRP = fr * 1e10;
1375 Number const deltaUSD = fr * 1e4;
1385 XRP(10'000) + depositXRP,
1386 USD(10'000) + depositUSD,
1510 STAmount{USD, UINT64_C(10'000'000001), -6},
1626 using namespace jtx;
1629 [&](
AMM& ammAlice,
Env& env) {
1639 [&](
AMM& ammAlice,
Env& env) {
1665 .asset1Out =
USD(100),
1794 for (
auto const& it : invalidOptions)
1805 ter(std::get<5>(it)));
1956 STAmount{
USD, UINT64_C(9'999'9999999999999), -13},
2061 [&](
AMM& ammAlice,
Env&) {
2073 [&](
AMM& ammAlice,
Env&) {
2144 using namespace jtx;
2149 auto const baseFee = env.
current()->fees().base.drops();
2313 [&](
AMM& ammAlice,
Env& env) {
2317 if (!env.
current()->rules().enabled(fixAMMv1_1))
2333 ammAlice.withdrawAll(
carol);
2339 {
all,
all - fixAMMv1_1});
2343 [&](AMM& ammAlice, Env& env) {
2344 ammAlice.deposit(carol, 1'000'000);
2346 carol, USD(0), std::nullopt, IOUAmount{520, 0});
2347 if (!env.current()->rules().enabled(fixAMMv1_1))
2349 ammAlice.expectBalances(
2350 XRPAmount(11'000'000'000),
2351 STAmount{USD, UINT64_C(9'372'781065088757), -12},
2352 IOUAmount{10'153'846'15384616, -8}) &&
2353 ammAlice.expectLPTokens(
2354 carol, IOUAmount{153'846'15384616, -8}));
2357 ammAlice.expectBalances(
2358 XRPAmount(11'000'000'000),
2359 STAmount{USD, UINT64_C(9'372'781065088769), -12},
2360 IOUAmount{10'153'846'15384616, -8}) &&
2361 ammAlice.expectLPTokens(
2362 carol, IOUAmount{153'846'15384616, -8}));
2367 {all, all - fixAMMv1_1});
2372 fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
2373 env(
rate(gw, 1.25));
2376 AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
2377 BEAST_EXPECT(ammAlice.expectBalances(
2378 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2379 BEAST_EXPECT(
expectLine(env, alice, USD(0)));
2380 BEAST_EXPECT(
expectLine(env, alice, BTC(0)));
2381 fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
2383 ammAlice.deposit(carol, 10);
2384 BEAST_EXPECT(ammAlice.expectBalances(
2385 USD(22'000), BTC(0.55), IOUAmount{110, 0}));
2386 BEAST_EXPECT(
expectLine(env, carol, USD(0)));
2387 BEAST_EXPECT(
expectLine(env, carol, BTC(0)));
2389 ammAlice.withdraw(carol, 10);
2390 BEAST_EXPECT(ammAlice.expectBalances(
2391 USD(20'000), BTC(0.5), IOUAmount{100, 0}));
2392 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
2393 BEAST_EXPECT(
expectLine(env, carol, USD(2'000)));
2394 BEAST_EXPECT(
expectLine(env, carol, BTC(0.05)));
2398 testAMM([&](AMM& ammAlice, Env&) {
2400 ammAlice.withdraw(alice, IOUAmount{1, -3});
2401 BEAST_EXPECT(ammAlice.expectBalances(
2402 XRPAmount{9'999'999'999},
2403 STAmount{USD, UINT64_C(9'999'999999), -6},
2404 IOUAmount{9'999'999'999, -3}));
2406 testAMM([&](AMM& ammAlice, Env&) {
2408 ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
2409 BEAST_EXPECT(ammAlice.expectBalances(
2410 XRPAmount{9'999'999'999},
2412 IOUAmount{9'999'999'9995, -4}));
2414 testAMM([&](AMM& ammAlice, Env&) {
2416 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
2417 BEAST_EXPECT(ammAlice.expectBalances(
2419 STAmount{USD, UINT64_C(9'999'9999999999), -10},
2420 IOUAmount{9'999'999'99999995, -8}));
2425 testAMM([&](AMM& ammAlice, Env&) {
2426 ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
2427 BEAST_EXPECT(ammAlice.expectBalances(
2428 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
2431 testAMM([&](AMM& ammAlice, Env&) {
2432 ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
2433 BEAST_EXPECT(ammAlice.expectBalances(
2434 XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
2437 testAMM([&](AMM& ammAlice, Env&) {
2438 ammAlice.withdraw(alice, IOUAmount{9'999'900},
XRP(0));
2439 BEAST_EXPECT(ammAlice.expectBalances(
2440 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2443 testAMM([&](AMM& ammAlice, Env&) {
2445 alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
2446 BEAST_EXPECT(ammAlice.expectBalances(
2447 XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
2450 testAMM([&](AMM& ammAlice, Env&) {
2451 ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
2452 BEAST_EXPECT(ammAlice.expectBalances(
2453 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2460 testcase(
"Invalid Fee Vote");
2461 using namespace jtx;
2463 testAMM([&](
AMM& ammAlice,
Env& env) {
2514 testAMM([&](
AMM& ammAlice,
Env& env) {
2529 testcase(
"Fee Vote");
2530 using namespace jtx;
2533 testAMM([&](
AMM& ammAlice,
Env& env) {
2535 ammAlice.
vote({}, 1'000);
2541 auto vote = [&](
AMM& ammAlice,
2544 int fundUSD = 100'000,
2548 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2550 ammAlice.
vote(a, 50 * (i + 1));
2556 testAMM([&](
AMM& ammAlice,
Env& env) {
2557 for (
int i = 0; i < 7; ++i)
2558 vote(ammAlice, env, i, 10'000);
2564 testAMM([&](
AMM& ammAlice,
Env& env) {
2565 for (
int i = 0; i < 7; ++i)
2566 vote(ammAlice, env, i);
2569 ammAlice.
vote(a, 450);
2575 testAMM([&](
AMM& ammAlice,
Env& env) {
2576 for (
int i = 0; i < 7; ++i)
2577 vote(ammAlice, env, i);
2579 vote(ammAlice, env, 7, 100'000, 20'000'000);
2585 testAMM([&](
AMM& ammAlice,
Env& env) {
2586 for (
int i = 7; i > 0; --i)
2587 vote(ammAlice, env, i);
2589 vote(ammAlice, env, 0, 100'000, 20'000'000);
2596 testAMM([&](
AMM& ammAlice,
Env& env) {
2598 for (
int i = 0; i < 7; ++i)
2599 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2601 for (
int i = 0; i < 7; ++i)
2603 ammAlice.
deposit(carol, 10'000'000);
2604 ammAlice.
vote(carol, 1'000);
2613 testAMM([&](
AMM& ammAlice,
Env& env) {
2615 for (
int i = 0; i < 7; ++i)
2616 vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2618 for (
int i = 0; i < 7; ++i)
2619 ammAlice.
withdraw(accounts[i], 9'000'000);
2620 ammAlice.
deposit(carol, 1'000);
2622 ammAlice.
vote(carol, 1'000);
2623 auto const info = ammAlice.
ammRpcInfo()[jss::amm][jss::vote_slots];
2625 BEAST_EXPECT(info[i][jss::account] != carol.human());
2634 testcase(
"Invalid Bid");
2635 using namespace jtx;
2641 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2642 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2645 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2652 .bidMin = 1'000'000,
2660 fund(env, gw, {alice},
XRP(2'000), {USD(2'000)});
2661 AMM amm(env, gw,
XRP(1'000), USD(1'000),
false, 1'000);
2664 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
2671 .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)},
2677 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{999'999}));
2681 amm.expectBalances(
XRP(1'000), USD(1'000),
IOUAmount{1}));
2684 BEAST_EXPECT(
Number{amm.getLPTokensBalance(gw)} == 1);
2696 testAMM([&](
AMM& ammAlice,
Env& env) {
2701 .flags = tfWithdrawAll,
2703 ter(temINVALID_FLAG));
2705 ammAlice.
deposit(carol, 1'000'000);
2707 for (
auto bid : {0, -100})
2713 ter(temBAD_AMOUNT));
2718 ter(temBAD_AMOUNT));
2727 ter(tecAMM_INVALID_TOKENS));
2737 ter(terNO_ACCOUNT));
2740 Account
const dan(
"dan");
2741 env.
fund(XRP(1'000), dan);
2746 ter(tecAMM_INVALID_TOKENS));
2750 ter(tecAMM_INVALID_TOKENS));
2756 .authAccounts = {bob},
2758 ter(terNO_ACCOUNT));
2764 .assets = {{USD, GBP}},
2771 .bidMax = STAmount{USD, 100},
2773 ter(temBAD_AMM_TOKENS));
2776 .bidMin = STAmount{USD, 100},
2778 ter(temBAD_AMM_TOKENS));
2782 testAMM([&](AMM& ammAlice, Env& env) {
2783 ammAlice.withdrawAll(alice);
2792 testAMM([&](AMM& ammAlice, Env& env) {
2794 Account bill(
"bill");
2795 Account scott(
"scott");
2796 Account james(
"james");
2797 env.fund(
XRP(1'000), bob, ed, bill, scott, james);
2799 ammAlice.deposit(carol, 1'000'000);
2803 .authAccounts = {bob, ed, bill, scott, james},
2809 testAMM([&](AMM& ammAlice, Env& env) {
2810 fund(env, gw, {bob},
XRP(1'000), {USD(100)}, Fund::Acct);
2811 ammAlice.deposit(carol, 1'000'000);
2812 ammAlice.deposit(bob, 10);
2815 .bidMin = 1'000'001,
2817 ter(tecAMM_INVALID_TOKENS));
2820 .bidMax = 1'000'001,
2822 ter(tecAMM_INVALID_TOKENS));
2827 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2832 ter(tecAMM_INVALID_TOKENS));
2838 fund(env, gw, {alice, bob},
XRP(1'000), {USD(1'000)});
2840 auto const lpIssue =
amm.lptIssue();
2841 env.trust(STAmount{lpIssue, 100}, alice);
2842 env.trust(STAmount{lpIssue, 50}, bob);
2843 env(
pay(gw, alice, STAmount{lpIssue, 100}));
2844 env(
pay(gw, bob, STAmount{lpIssue, 50}));
2845 env(
amm.bid({.account = alice, .bidMin = 100}));
2852 ter(tecAMM_FAILED));
2860 using namespace jtx;
2867 [&](
AMM& ammAlice,
Env& env) {
2868 ammAlice.
deposit(carol, 1'000'000);
2869 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2882 [&](
AMM& ammAlice,
Env& env) {
2883 ammAlice.
deposit(carol, 1'000'000);
2886 {.account = carol, .bidMin = 110, .bidMax = 110}));
2892 {.account = alice, .bidMin = 180, .bidMax = 200}));
2895 XRP(11'000), USD(11'000),
IOUAmount{10'999'814'5, -1}));
2904 [&](
AMM& ammAlice,
Env& env) {
2905 ammAlice.
deposit(carol, 1'000'000);
2907 env(ammAlice.
bid({.account = carol, .bidMin = 110}));
2910 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2911 ammAlice.
deposit(bob, 1'000'000);
2913 env(ammAlice.
bid({.account = bob}));
2924 env(ammAlice.
bid({.account = carol, .bidMax = 600}));
2938 {.account = carol, .bidMin = 100, .bidMax = 600}));
2949 [&](
AMM& ammAlice,
Env& env) {
2950 ammAlice.
deposit(carol, 1'000'000);
2952 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2953 ammAlice.
deposit(bob, 1'000'000);
2958 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2962 env(ammAlice.
bid({.account = bob}));
2968 env(ammAlice.
bid({.account = carol}));
2974 env(ammAlice.
bid({.account = bob}));
2980 0, std::nullopt,
IOUAmount{127'33875, -5}));
2983 env(ammAlice.
bid({.account = carol, .bidMin = 110})).
close();
2988 XRP(12'000), USD(12'000),
IOUAmount{11'999'678'91, -2}));
3000 [&](
AMM& ammAlice,
Env& env) {
3003 fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
3004 ammAlice.
deposit(bob, 1'000'000);
3005 ammAlice.
deposit(ed, 1'000'000);
3006 ammAlice.
deposit(carol, 500'000);
3007 ammAlice.
deposit(dan, 500'000);
3012 .authAccounts = {bob, ed},
3014 auto const slotPrice =
IOUAmount{5'200};
3015 ammTokens -= slotPrice;
3018 XRP(13'000), USD(13'000), ammTokens));
3020 for (
int i = 0; i < 10; ++i)
3022 auto tokens = ammAlice.
deposit(carol, USD(100));
3023 ammAlice.
withdraw(carol, tokens, USD(0));
3024 tokens = ammAlice.
deposit(bob, USD(100));
3025 ammAlice.
withdraw(bob, tokens, USD(0));
3026 tokens = ammAlice.
deposit(ed, USD(100));
3027 ammAlice.
withdraw(ed, tokens, USD(0));
3030 if (!features[fixAMMv1_1])
3034 STAmount(USD, UINT64_C(29'499'00572620545), -11));
3037 STAmount(USD, UINT64_C(18'999'00572616195), -11));
3040 STAmount(USD, UINT64_C(18'999'00572611841), -11));
3044 STAmount(USD, UINT64_C(13'002'98282151419), -11),
3051 STAmount(USD, UINT64_C(29'499'00572620544), -11));
3054 STAmount(USD, UINT64_C(18'999'00572616194), -11));
3057 STAmount(USD, UINT64_C(18'999'0057261184), -10));
3061 STAmount(USD, UINT64_C(13'002'98282151422), -11),
3066 for (
int i = 0; i < 10; ++i)
3068 auto const tokens = ammAlice.
deposit(dan, USD(100));
3069 ammAlice.
withdraw(dan, tokens, USD(0));
3074 if (!features[fixAMMv1_1])
3078 STAmount(USD, UINT64_C(19'490'056722744), -9));
3082 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3085 ammAlice.
deposit(carol, USD(100));
3089 STAmount{USD, UINT64_C(13'112'92609877019), -11},
3091 env(pay(carol, bob, USD(100)),
3099 STAmount{USD, UINT64_C(13'012'92609877019), -11},
3106 STAmount(USD, UINT64_C(19'490'05672274399), -11));
3110 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3113 ammAlice.
deposit(carol, USD(100));
3117 STAmount{USD, UINT64_C(13'112'92609877023), -11},
3119 env(pay(carol, bob, USD(100)),
3127 STAmount{USD, UINT64_C(13'012'92609877023), -11},
3136 if (!features[fixAMMv1_1])
3140 STAmount{USD, UINT64_C(13'114'03663047264), -11},
3147 STAmount{USD, UINT64_C(13'114'03663047269), -11},
3154 if (!features[fixAMMv1_1])
3157 STAmount(USD, UINT64_C(29'399'00572620545), -11));
3161 STAmount(USD, UINT64_C(29'399'00572620544), -11));
3163 for (
int i = 0; i < 10; ++i)
3165 auto const tokens = ammAlice.
deposit(carol, USD(100));
3166 ammAlice.
withdraw(carol, tokens, USD(0));
3170 if (!features[fixAMMv1_1])
3174 STAmount(USD, UINT64_C(29'389'06197177128), -11));
3177 STAmount{USD, UINT64_C(13'123'98038490681), -11},
3184 STAmount(USD, UINT64_C(29'389'06197177124), -11));
3187 STAmount{USD, UINT64_C(13'123'98038490689), -11},
3195 if (!features[fixAMMv1_1])
3199 STAmount{USD, UINT64_C(13'023'98038490681), -11},
3206 STAmount{USD, UINT64_C(13'023'98038490689), -11},
3217 [&](
AMM& ammAlice,
Env& env) {
3220 Number{STAmount::cMinValue, STAmount::cMinOffset};
3222 {.account = alice, .bidMin = IOUAmount{tiny}}));
3228 XRP(10'000), USD(10'000), ammAlice.
tokens()));
3233 IOUAmount{STAmount::cMinValue, STAmount::cMinOffset},
3237 0, 0, IOUAmount{tiny * Number{105, -2}}));
3241 XRP(10'000), USD(10'000), ammAlice.
tokens()));
3250 [&](AMM& ammAlice, Env& env) {
3253 .bidMin = IOUAmount{100},
3254 .authAccounts = {carol},
3256 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
3257 env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}}));
3261 fund(env, {bob, dan},
XRP(1'000));
3264 .bidMin = IOUAmount{100},
3265 .authAccounts = {bob, dan},
3276 Env env(*
this, features);
3277 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3278 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3279 auto const lpIssue =
amm.lptIssue();
3280 env.
trust(STAmount{lpIssue, 500}, alice);
3281 env.
trust(STAmount{lpIssue, 50}, bob);
3282 env(
pay(gw, alice, STAmount{lpIssue, 500}));
3283 env(
pay(gw, bob, STAmount{lpIssue, 50}));
3285 env(
amm.bid({.account = alice, .bidMin = 500}));
3286 BEAST_EXPECT(
amm.expectAuctionSlot(100, 0, IOUAmount{500}));
3287 BEAST_EXPECT(
expectLine(env, alice, STAmount{lpIssue, 0}));
3290 env(
pay(alice, bob, USD(10)), path(~USD), sendmax(
XRP(11)));
3291 BEAST_EXPECT(
amm.expectBalances(
3292 XRPAmount{1'010'010'011},
3294 IOUAmount{1'004'487'562112089, -9}));
3296 env(
pay(bob, alice,
XRP(10)), path(~XRP), sendmax(USD(11)));
3297 if (!features[fixAMMv1_1])
3299 BEAST_EXPECT(
amm.expectBalances(
3300 XRPAmount{1'000'010'011},
3301 STAmount{USD, UINT64_C(1'010'10090898081), -11},
3302 IOUAmount{1'004'487'562112089, -9}));
3306 BEAST_EXPECT(
amm.expectBalances(
3307 XRPAmount{1'000'010'011},
3308 STAmount{USD, UINT64_C(1'010'100908980811), -12},
3309 IOUAmount{1'004'487'562112089, -9}));
3315 Env env(*
this, features);
3316 auto const baseFee = env.
current()->fees().base;
3318 fund(env, gw, {alice, bob},
XRP(2'000), {USD(2'000)});
3319 AMM amm(env, gw,
XRP(1'000), USD(1'010),
false, 1'000);
3323 auto jtx = env.
jt(tx, seq(1), fee(baseFee));
3325 PreflightContext pfctx(
3331 auto pf = AMMBid::preflight(pfctx);
3332 BEAST_EXPECT(pf == temDISABLED);
3337 auto jtx = env.
jt(tx, seq(1), fee(baseFee));
3338 jtx.
jv[
"TxnSignature"] =
"deadbeef";
3339 jtx.stx = env.
ust(jtx);
3340 PreflightContext pfctx(
3346 auto pf = AMMBid::preflight(pfctx);
3347 BEAST_EXPECT(pf != tesSUCCESS);
3351 auto jtx = env.
jt(tx, seq(1), fee(baseFee));
3352 jtx.
jv[
"Asset2"][
"currency"] =
"XRP";
3354 jtx.stx = env.
ust(jtx);
3355 PreflightContext pfctx(
3361 auto pf = AMMBid::preflight(pfctx);
3362 BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
3370 testcase(
"Invalid AMM Payment");
3371 using namespace jtx;
3373 using namespace std::literals::chrono_literals;
3377 for (
auto const& acct : {gw, alice})
3381 fund(env, gw, {alice, carol},
XRP(1'000), {USD(100)});
3383 AMM ammAlice(env, acct,
XRP(10), USD(10));
3385 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3388 env(pay(carol, ammAlice.ammAccount(),
XRP(300)),
3391 env(pay(carol, ammAlice.ammAccount(), USD(10)),
3396 fund(env, gw, {alice, carol},
XRP(10'000'000), {USD(10'000)});
3398 AMM ammAlice(env, acct,
XRP(1'000'000), USD(100));
3400 env(pay(carol, ammAlice.ammAccount(),
XRP(10)),
3403 env(pay(carol, ammAlice.ammAccount(),
XRP(1'000'000)),
3409 testAMM([&](
AMM& ammAlice,
Env& env) {
3410 auto const baseFee = env.
current()->fees().base;
3420 testAMM([&](
AMM& ammAlice,
Env& env) {
3421 auto const pk = carol.pk();
3422 auto const settleDelay = 100s;
3424 env.
current()->info().parentCloseTime + 200s;
3436 testAMM([&](
AMM& ammAlice,
Env& env) {
3443 [&](
AMM& ammAlice,
Env& env) {
3445 env(pay(alice, carol, USD(100)),
3449 env(pay(alice, carol,
XRP(100)),
3456 STAmount{USD, UINT64_C(99'999999999), -9}),
3462 STAmount{USD, UINT64_C(999'99999999), -8}),
3471 env(pay(alice, carol, USD(99.99)),
3480 {{
XRP(100), USD(100)}});
3483 testAMM([&](
AMM& ammAlice,
Env& env) {
3486 env(pay(alice, carol, USD(1)),
3491 env(pay(alice, carol,
XRP(1)),
3499 testAMM([&](
AMM& ammAlice,
Env& env) {
3505 env(pay(alice, carol, USD(1)),
3510 env(pay(alice, carol,
XRP(1)),
3518 testAMM([&](
AMM& ammAlice,
Env& env) {
3522 env(pay(alice, carol,
XRP(1)),
3533 testcase(
"Basic Payment");
3534 using namespace jtx;
3539 [&](
AMM& ammAlice,
Env& env) {
3540 env.
fund(jtx::XRP(30'000), bob);
3542 env(pay(bob, carol, USD(100)),
3548 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3550 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3553 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3555 {{
XRP(10'000), USD(10'100)}},
3562 [&](
AMM& ammAlice,
Env& env) {
3563 env.
fund(jtx::XRP(30'000), bob);
3565 env(pay(bob, carol, USD(100)),
sendmax(
XRP(100)));
3568 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3570 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3573 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3575 {{
XRP(10'000), USD(10'100)}},
3583 [&](
AMM& ammAlice,
Env& env) {
3584 env.
fund(jtx::XRP(30'000), bob);
3589 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3591 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
3594 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3596 {{
XRP(10'000), USD(10'100)}},
3603 [&](
AMM& ammAlice,
Env& env) {
3604 env.
fund(jtx::XRP(30'000), bob);
3608 env(pay(bob, carol, USD(100)),
3615 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3617 BEAST_EXPECT(
expectLine(env, carol, USD(30'010)));
3621 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3625 env(pay(bob, carol, USD(100)),
3633 {{
XRP(10'000), USD(10'010)}},
3640 [&](
AMM& ammAlice,
Env& env) {
3643 env.
fund(jtx::XRP(30'000), bob);
3648 env(pay(bob, carol, USD(100)),
3655 XRP(10'010), USD(10'000), ammAlice.
tokens()));
3660 STAmount{USD, UINT64_C(30'009'09090909091), -11}));
3662 env, bob,
XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3664 {{
XRP(10'000), USD(10'010)}},
3671 [&](
AMM& ammAlice,
Env& env) {
3672 env.
fund(jtx::XRP(30'000), bob);
3674 env(pay(bob, carol, USD(100)),
3680 {{
XRP(10'000), USD(10'000)}},
3690 Env env(*
this, features);
3692 env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
3696 auto ammEUR_XRP =
AMM(env, alice,
XRP(10'000), EUR(10'000));
3697 auto ammUSD_EUR =
AMM(env, alice, EUR(10'000), USD(10'000));
3700 env(pay(bob, carol, USD(100)),
3705 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3707 STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3708 ammEUR_XRP.tokens()));
3709 if (!features[fixAMMv1_1])
3711 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3712 STAmount(USD, UINT64_C(9'970'097277662122), -12),
3713 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3714 ammUSD_EUR.tokens()));
3717 Amounts
const expectedAmounts =
3718 env.
closed()->rules().enabled(fixReducedOffersV2)
3719 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233787816), -14)}
3722 STAmount(USD, UINT64_C(29'90272233787818), -14)};
3724 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3728 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3729 STAmount(USD, UINT64_C(9'970'097277662172), -12),
3730 STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3731 ammUSD_EUR.tokens()));
3734 Amounts
const expectedAmounts =
3735 env.
closed()->rules().enabled(fixReducedOffersV2)
3736 ? Amounts{
XRPAmount(30'201'749),
STAmount(USD, UINT64_C(29'90272233782839), -14)}
3739 STAmount(USD, UINT64_C(29'90272233782840), -14)};
3741 BEAST_EXPECT(
expectOffers(env, alice, 1, {{expectedAmounts}}));
3757 [&](
AMM& ammAlice,
Env& env) {
3760 env.
trust(EUR(2'000), alice);
3762 env(pay(gw, alice, EUR(1'000)));
3767 env(pay(bob, carol, USD(100)),
3774 STAmount(USD, UINT64_C(9'950'01249687578), -11),
3782 STAmount(EUR, UINT64_C(49'98750312422), -11)},
3784 STAmount(EUR, UINT64_C(49'98750312422), -11),
3785 STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
3790 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3807 [&](
AMM& ammAlice,
Env& env) {
3808 fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3812 env(pay(alice, carol, USD(200)),
3816 if (!features[fixAMMv1_1])
3819 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3821 BEAST_EXPECT(
expectLine(env, carol, USD(30'200)));
3827 STAmount(USD, UINT64_C(10'000'00000000001), -11),
3832 STAmount(USD, UINT64_C(30'199'99999999999), -11)));
3840 ammCrtFee(env) -
txfee(env, 1)));
3843 {{
XRP(10'000), USD(10'100)}},
3852 Env env(*
this, features);
3853 fund(env, gw, {alice, bob, carol},
XRP(20'000), {USD(2'000)});
3857 AMM ammAlice(env, alice,
XRP(1'000), USD(1'050));
3858 env(pay(alice, carol, USD(200)),
3863 XRP(1'050), USD(1'000), ammAlice.
tokens()));
3864 BEAST_EXPECT(
expectLine(env, carol, USD(2'200)));
3870 [&](
AMM& ammAlice,
Env& env) {
3871 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
3873 env(offer(bob, USD(100),
XRP(100)));
3876 XRP(10'100), USD(10'000), ammAlice.
tokens()));
3878 BEAST_EXPECT(
expectLine(env, bob, USD(1'100)));
3881 env, bob,
XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3884 {{
XRP(10'000), USD(10'100)}},
3892 [&](
AMM& ammAlice,
Env& env) {
3893 env(
rate(gw, 1.25));
3899 env(offer(carol, EUR(100), GBP(100)));
3903 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
3905 BEAST_EXPECT(
expectLine(env, carol, GBP(29'875)));
3907 BEAST_EXPECT(
expectLine(env, carol, EUR(30'100)));
3910 {{GBP(1'000), EUR(1'100)}},
3916 [&](
AMM& amm,
Env& env) {
3917 env(
rate(gw, 1.001));
3919 env(offer(carol,
XRP(100), USD(55)));
3921 if (!features[fixAMMv1_1])
3931 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
3933 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
3944 BEAST_EXPECT(amm.expectBalances(
3946 STAmount{USD, UINT64_C(550'000000055), -9},
3955 STAmount{USD, 4'99999995, -8}}}}));
3959 STAmount(USD, UINT64_C(29'949'94999999494), -11));
3962 {{
XRP(1'000), USD(500)}},
3967 [&](
AMM& amm,
Env& env) {
3968 env(
rate(gw, 1.001));
3970 env(offer(carol,
XRP(10), USD(5.5)));
3972 if (!features[fixAMMv1_1])
3974 BEAST_EXPECT(amm.expectBalances(
3976 STAmount{USD, UINT64_C(505'050505050505), -12},
3982 BEAST_EXPECT(amm.expectBalances(
3984 STAmount{USD, UINT64_C(505'0505050505051), -13},
3989 {{
XRP(1'000), USD(500)}},
3995 [&](
AMM& ammAlice,
Env& env) {
4002 {GBP(2'000), EUR(2'000)},
4004 env(
rate(gw, 1.25));
4015 env(offer(carol, EUR(100), GBP(100)));
4017 if (!features[fixAMMv1_1])
4026 STAmount{GBP, UINT64_C(1'037'06583722133), -11},
4027 STAmount{EUR, UINT64_C(1'060'684828792831), -12},
4035 STAmount{EUR, UINT64_C(50'684828792831), -12},
4036 STAmount{GBP, UINT64_C(50'684828792831), -12}}}));
4048 STAmount{GBP, UINT64_C(29'941'16770347333), -11}));
4053 STAmount{EUR, UINT64_C(30'049'31517120716), -11}));
4065 STAmount{GBP, UINT64_C(1'060'684828792832), -12},
4066 STAmount{EUR, UINT64_C(1'037'06583722134), -11},
4074 STAmount{EUR, UINT64_C(27'06583722134028), -14},
4075 STAmount{GBP, UINT64_C(27'06583722134028), -14}}}));
4087 STAmount{GBP, UINT64_C(29'911'64396400896), -11}));
4092 STAmount{EUR, UINT64_C(30'072'93416277865), -11}));
4095 BEAST_EXPECT(
expectLine(env, bob, GBP(2'010)));
4097 BEAST_EXPECT(
expectLine(env, ed, EUR(1'987.5)));
4099 {{GBP(1'000), EUR(1'100)}},
4112 [&](
AMM& ammAlice,
Env& env) {
4113 fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
4114 env(
rate(gw, 1.25));
4116 env(pay(bob, carol, EUR(100)),
4122 GBP(1'100), EUR(1'000), ammAlice.
tokens()));
4124 BEAST_EXPECT(
expectLine(env, carol, EUR(30'080)));
4126 {{GBP(1'000), EUR(1'100)}},
4143 [&](
AMM& ammAlice,
Env& env) {
4146 auto const CAN = gw[
"CAN"];
4147 fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
4148 fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
4149 fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
4150 env(trust(carol, USD(100)));
4151 env(
rate(gw, 1.25));
4153 env(offer(dan, CAN(200), GBP(200)));
4154 env(offer(ed, EUR(200), USD(200)));
4156 env(pay(bob, carol, USD(100)),
4157 path(~GBP, ~EUR, ~USD),
4162 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25), GBP(43.75)));
4164 GBP(10'125), EUR(10'000), ammAlice.
tokens()));
4165 BEAST_EXPECT(
expectLine(env, ed, EUR(300), USD(100)));
4166 BEAST_EXPECT(
expectLine(env, carol, USD(80)));
4168 {{GBP(10'000), EUR(10'125)}},
4175 [&](
AMM& ammAlice,
Env& env) {
4176 env(pay(alice, carol, USD(99.99)),
4181 env(pay(alice, carol, USD(100)),
4186 env(pay(alice, carol,
XRP(100)),
4197 {{
XRP(100), USD(100)}},
4204 Env env(*
this, features);
4205 auto const ETH = gw[
"ETH"];
4211 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4212 fund(env, gw, {carol, bob},
XRP(1'000), {USD(200)}, Fund::Acct);
4213 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4214 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4215 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4216 AMM xrp_usd(env, alice,
XRP(10'150), USD(10'200));
4217 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4218 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4219 AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
4220 env(pay(bob, carol, USD(100)),
4221 path(~EUR, ~BTC, ~USD),
4223 path(~ETH, ~EUR, ~USD),
4225 if (!features[fixAMMv1_1])
4231 STAmount{ETH, UINT64_C(10'073'65779244494), -11},
4234 STAmount{ETH, UINT64_C(10'926'34220755506), -11},
4235 STAmount{EUR, UINT64_C(10'973'54232078752), -11},
4238 STAmount{EUR, UINT64_C(10'126'45767921248), -11},
4239 STAmount{USD, UINT64_C(9'973'93151712086), -11},
4245 STAmount{USD, UINT64_C(10'126'06848287914), -11},
4252 STAmount{ETH, UINT64_C(10'073'65779244461), -11},
4255 STAmount{ETH, UINT64_C(10'926'34220755539), -11},
4256 STAmount{EUR, UINT64_C(10'973'5423207872), -10},
4259 STAmount{EUR, UINT64_C(10'126'4576792128), -10},
4260 STAmount{USD, UINT64_C(9'973'93151712057), -11},
4266 STAmount{USD, UINT64_C(10'126'06848287943), -11},
4276 BEAST_EXPECT(xrp_eur.expectBalances(
4277 XRP(10'100), EUR(10'000), xrp_eur.tokens()));
4279 EUR(10'000), BTC(10'200), eur_btc.
tokens()));
4281 BTC(10'100), USD(10'000), btc_usd.
tokens()));
4283 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4288 Env env(*
this, features);
4289 auto const ETH = gw[
"ETH"];
4295 {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
4296 fund(env, gw, {carol, bob},
XRP(1000), {USD(200)}, Fund::Acct);
4297 AMM xrp_eur(env, alice,
XRP(10'100), EUR(10'000));
4298 AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
4299 AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
4300 AMM xrp_eth(env, alice,
XRP(10'000), ETH(10'100));
4301 AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
4302 env(pay(bob, carol, USD(100)),
4303 path(~EUR, ~BTC, ~USD),
4304 path(~ETH, ~EUR, ~BTC, ~USD),
4306 if (!features[fixAMMv1_1])
4310 BEAST_EXPECT(xrp_eur.expectBalances(
4312 STAmount{EUR, UINT64_C(9'981'544436337968), -12},
4315 STAmount{EUR, UINT64_C(10'101'16096785173), -11},
4316 STAmount{BTC, UINT64_C(10'097'91426968066), -11},
4319 STAmount{BTC, UINT64_C(10'202'08573031934), -11},
4324 STAmount{ETH, UINT64_C(10'017'41072778012), -11},
4327 STAmount{ETH, UINT64_C(10'982'58927221988), -11},
4328 STAmount{EUR, UINT64_C(10'917'2945958103), -10},
4333 BEAST_EXPECT(xrp_eur.expectBalances(
4335 STAmount{EUR, UINT64_C(9'981'544436337923), -12},
4338 STAmount{EUR, UINT64_C(10'101'16096785188), -11},
4339 STAmount{BTC, UINT64_C(10'097'91426968059), -11},
4342 STAmount{BTC, UINT64_C(10'202'08573031941), -11},
4347 STAmount{ETH, UINT64_C(10'017'41072777996), -11},
4350 STAmount{ETH, UINT64_C(10'982'58927222004), -11},
4351 STAmount{EUR, UINT64_C(10'917'2945958102), -10},
4354 BEAST_EXPECT(
expectLine(env, carol, USD(300)));
4360 [&](
AMM& ammAlice,
Env& env) {
4362 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4363 env(trust(alice, EUR(200)));
4364 for (
int i = 0; i < 30; ++i)
4365 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4368 env(offer(alice, EUR(140),
XRP(100)));
4369 env(pay(bob, carol, USD(100)),
4373 if (!features[fixAMMv1_1])
4378 STAmount{USD, UINT64_C(9'970'089730807577), -12},
4383 STAmount{USD, UINT64_C(30'029'91026919241), -11}));
4389 STAmount{USD, UINT64_C(9'970'089730807827), -12},
4394 STAmount{USD, UINT64_C(30'029'91026919217), -11}));
4405 [&](
AMM& ammAlice,
Env& env) {
4407 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
4408 env(trust(alice, EUR(200)));
4409 for (
int i = 0; i < 29; ++i)
4410 env(offer(alice, EUR(1.0 + 0.01 * i),
XRP(1)));
4413 env(offer(alice, EUR(140),
XRP(100)));
4414 env(pay(bob, carol, USD(100)),
4420 if (!features[fixAMMv1_1])
4426 STAmount{USD, UINT64_C(30'099'99999999999), -11}));
4430 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4436 {{{
STAmount{EUR, UINT64_C(39'1858572), -7},
4447 Env env(*
this, features);
4448 fund(env, gw, {alice, carol, bob},
XRP(30'000), {USD(30'000)});
4449 env(offer(bob,
XRP(100), USD(100.001)));
4450 AMM ammAlice(env, alice,
XRP(10'000), USD(10'100));
4451 env(offer(carol, USD(100),
XRP(100)));
4452 if (!features[fixAMMv1_1])
4456 STAmount{USD, UINT64_C(10'049'92586949302), -11},
4463 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
4469 STAmount{USD, UINT64_C(10'049'92587049303), -11},
4476 STAmount{USD, UINT64_C(50'07512950697), -11}}}}));
4477 BEAST_EXPECT(
expectLine(env, carol, USD(30'100)));
4483 [&](
AMM& ammAlice,
Env& env) {
4487 env(pay(alice, carol, USD(1)),
4502 testcase(
"AMM Tokens");
4503 using namespace jtx;
4506 testAMM([&](
AMM& ammAlice,
Env& env) {
4507 auto const baseFee = env.
current()->fees().base.drops();
4508 auto const token1 = ammAlice.
lptIssue();
4515 env(offer(carol,
STAmount{token1, 5'000'000}, priceXRP));
4517 env(offer(alice, priceXRP,
STAmount{token1, 5'000'000}));
4525 ammAlice.
vote(carol, 1'000);
4527 ammAlice.
vote(carol, 0);
4530 env(ammAlice.
bid({.account = carol, .bidMin = 100}));
4555 testAMM([&](
AMM& ammAlice,
Env& env) {
4556 ammAlice.
deposit(carol, 1'000'000);
4557 fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
4558 AMM ammAlice1(env, alice,
XRP(10'000), EUR(10'000));
4559 ammAlice1.deposit(carol, 1'000'000);
4560 auto const token1 = ammAlice.
lptIssue();
4561 auto const token2 = ammAlice1.lptIssue();
4581 testAMM([&](
AMM& ammAlice,
Env& env) {
4582 auto const token1 = ammAlice.
lptIssue();
4585 ammAlice.
deposit(carol, 1'000'000);
4591 env(pay(alice, carol,
STAmount{token1, 100}));
4601 env(pay(carol, alice,
STAmount{token1, 100}));
4613 testcase(
"Amendment");
4614 using namespace jtx;
4619 all - featureAMM - fixUniversalNumber};
4621 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
4623 Env env{*
this, feature};
4624 fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
4642 using namespace jtx;
4644 testAMM([&](
AMM& ammAlice,
Env& env) {
4645 auto const info = env.
rpc(
4649 "{\"account\": \"" + to_string(ammAlice.
ammAccount()) +
4652 info[jss::result][jss::account_data][jss::Flags].asUInt();
4662 testcase(
"Rippling");
4663 using namespace jtx;
4679 auto const TSTA = A[
"TST"];
4680 auto const TSTB = B[
"TST"];
4689 env.
trust(TSTA(10'000), C);
4690 env.
trust(TSTB(10'000), C);
4691 env(pay(A, C, TSTA(10'000)));
4692 env(pay(B, C, TSTB(10'000)));
4693 AMM amm(env, C, TSTA(5'000), TSTB(5'000));
4694 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
4701 env(pay(C, D,
STAmount{ammIss, 10}),
4703 path(amm.ammAccount()),
4712 testcase(
"AMMAndCLOB, offer quality change");
4713 using namespace jtx;
4714 auto const gw =
Account(
"gw");
4715 auto const TST = gw[
"TST"];
4716 auto const LP1 =
Account(
"LP1");
4717 auto const LP2 =
Account(
"LP2");
4719 auto prep = [&](
auto const& offerCb,
auto const& expectCb) {
4720 Env env(*
this, features);
4721 env.
fund(
XRP(30'000'000'000), gw);
4722 env(offer(gw,
XRP(11'500'000'000), TST(1'000'000'000)));
4726 env(offer(LP1, TST(25),
XRPAmount(287'500'000)));
4731 env(offer(LP2, TST(25),
XRPAmount(287'500'000)));
4743 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
4749 lp2TakerGets = offer[
"taker_gets"].asString();
4750 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
4755 if (!features[fixAMMv1_1])
4759 STAmount{TST, UINT64_C(1'68737984885388), -14}),
4765 STAmount{TST, UINT64_C(1'68737976189735), -14}),
4774 BEAST_EXPECT(lp2TakerGets == offer[
"taker_gets"].asString());
4776 lp2TakerPays == offer[
"taker_pays"][
"value"].asString());
4783 testcase(
"Trading Fee");
4784 using namespace jtx;
4788 [&](
AMM& ammAlice,
Env& env) {
4790 ammAlice.
deposit(carol, USD(3'000));
4794 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4796 ammAlice.
vote(alice, 1'000);
4800 ammAlice.
deposit(carol, USD(3'000));
4802 carol,
IOUAmount{994'981155689671, -12}));
4803 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4805 ammAlice.
vote(alice, 0);
4811 STAmount{USD, UINT64_C(29'994'96220068281), -11}));
4813 {{USD(1'000), EUR(1'000)}},
4821 [&](
AMM& ammAlice,
Env& env) {
4823 auto tokensFee = ammAlice.
deposit(
4824 carol, USD(1'000), std::nullopt,
STAmount{USD, 1, -1});
4827 ammAlice.
vote(alice, 0);
4829 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4832 BEAST_EXPECT(tokensFee ==
IOUAmount(485'636'0611129, -7));
4833 BEAST_EXPECT(tokensNoFee ==
IOUAmount(487'644'85901109, -8));
4843 [&](
AMM& ammAlice,
Env& env) {
4845 auto const tokensFee = ammAlice.
deposit(
4846 carol, USD(200), std::nullopt,
STAmount{USD, 2020, -6});
4849 ammAlice.
vote(alice, 0);
4851 auto const tokensNoFee = ammAlice.
deposit(carol, deposit);
4854 BEAST_EXPECT(tokensFee ==
IOUAmount(98'000'00000002, -8));
4855 BEAST_EXPECT(tokensNoFee ==
IOUAmount(98'475'81871545, -8));
4864 [&](
AMM& ammAlice,
Env& env) {
4866 ammAlice.
deposit(carol, USD(3'000));
4869 BEAST_EXPECT(
expectLine(env, carol, USD(27'000)));
4871 ammAlice.
vote(alice, 1'000);
4878 STAmount{USD, UINT64_C(29'994'97487437186), -11}));
4880 {{USD(1'000), EUR(1'000)}},
4887 [&](
AMM& ammAlice,
Env& env) {
4888 ammAlice.
deposit(carol, 1'000'000);
4889 auto const tokensFee = ammAlice.
withdraw(
4890 carol, USD(100), std::nullopt,
IOUAmount{520, 0});
4892 auto const balanceAfterWithdraw = [&]() {
4893 if (!features[fixAMMv1_1])
4894 return STAmount(USD, UINT64_C(30'443'43891402715), -11);
4895 return STAmount(USD, UINT64_C(30'443'43891402714), -11);
4897 BEAST_EXPECT(env.
balance(carol, USD) == balanceAfterWithdraw);
4899 auto const deposit = balanceAfterWithdraw - USD(29'000);
4900 ammAlice.
deposit(carol, deposit);
4902 ammAlice.
vote(alice, 0);
4904 auto const tokensNoFee = ammAlice.
withdraw(carol, deposit);
4905 if (!features[fixAMMv1_1])
4908 STAmount(USD, UINT64_C(30'443'43891402717), -11));
4912 STAmount(USD, UINT64_C(30'443'43891402716), -11));
4915 if (!features[fixAMMv1_1])
4917 tokensNoFee ==
IOUAmount(746'579'80779913, -8));
4920 tokensNoFee ==
IOUAmount(746'579'80779912, -8));
4921 BEAST_EXPECT(tokensFee ==
IOUAmount(750'588'23529411, -8));
4930 [&](
AMM& ammAlice,
Env& env) {
4936 {USD(1'000), EUR(1'000)},
4939 BEAST_EXPECT(
expectLine(env, alice, EUR(28'990)));
4940 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4941 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4943 env(pay(carol, alice, EUR(10)),
4949 BEAST_EXPECT(
expectLine(env, alice, EUR(29'000)));
4950 BEAST_EXPECT(
expectLine(env, alice, USD(29'000)));
4951 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4954 ammAlice.
vote(alice, 1'000);
4957 env(pay(bob, carol, USD(10)),
4964 env, bob,
STAmount{EUR, UINT64_C(989'8989898989899), -13}));
4966 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
4969 STAmount{EUR, UINT64_C(1'010'10101010101), -11},
4972 {{USD(1'000), EUR(1'010)}},
4979 [&](
AMM& ammAlice,
Env& env) {
4981 env(offer(carol, EUR(10), USD(10)));
4983 BEAST_EXPECT(
expectLine(env, carol, USD(29'990)));
4984 BEAST_EXPECT(
expectLine(env, carol, EUR(30'010)));
4986 env(offer(carol, USD(10), EUR(10)));
4989 ammAlice.
vote(alice, 500);
4991 env(offer(carol, EUR(10), USD(10)));
4998 STAmount{USD, UINT64_C(29'995'02512562814), -11}));
5002 STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
5008 STAmount{EUR, UINT64_C(5'025125628140703), -15},
5009 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
5010 if (!features[fixAMMv1_1])
5013 STAmount{USD, UINT64_C(1'004'974874371859), -12},
5014 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5020 STAmount{USD, UINT64_C(1'004'97487437186), -11},
5021 STAmount{EUR, UINT64_C(1'005'025125628141), -12},
5025 {{USD(1'000), EUR(1'010)}},
5035 Env env(*
this, features);
5040 {alice, bob, carol, ed},
5042 {USD(2'000), EUR(2'000)});
5043 env(offer(carol, EUR(5), USD(5)));
5044 AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
5045 env(pay(bob, ed, USD(10)),
5049 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5050 if (!features[fixAMMv1_1])
5052 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5054 USD(1'000), EUR(1'005), ammAlice.
tokens()));
5059 env, bob,
STAmount(EUR, UINT64_C(1989'999999999999), -12)));
5062 STAmount(EUR, UINT64_C(1005'000000000001), -12),
5071 Env env(*
this, features);
5076 {alice, bob, carol, ed},
5078 {USD(2'000), EUR(2'000)});
5079 env(offer(carol, EUR(5), USD(5)));
5081 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 250);
5082 env(pay(bob, ed, USD(10)),
5086 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5087 if (!features[fixAMMv1_1])
5092 STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
5095 STAmount{EUR, UINT64_C(1'005'012546992382), -12},
5103 STAmount{EUR, UINT64_C(1'989'987453007628), -12}));
5106 STAmount{EUR, UINT64_C(1'005'012546992372), -12},
5116 Env env(*
this, features);
5121 {alice, bob, carol, ed},
5123 {USD(2'000), EUR(2'000)});
5124 env(offer(carol, EUR(10), USD(10)));
5126 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5127 env(pay(bob, ed, USD(10)),
5131 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5132 BEAST_EXPECT(
expectLine(env, bob, EUR(1'990)));
5134 USD(1'005), EUR(1'000), ammAlice.
tokens()));
5143 Env env(*
this, features);
5148 {alice, bob, carol, ed},
5150 {USD(2'000), EUR(2'000)});
5151 env(offer(carol, EUR(9), USD(9)));
5153 AMM ammAlice(env, alice, USD(1'005), EUR(1'000),
false, 1'000);
5154 env(pay(bob, ed, USD(10)),
5158 BEAST_EXPECT(
expectLine(env, ed, USD(2'010)));
5160 env, bob,
STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
5163 STAmount{EUR, UINT64_C(1'001'006076703288), -12},
5172 testcase(
"Adjusted Deposit/Withdraw Tokens");
5174 using namespace jtx;
5178 [&](
AMM& ammAlice,
Env& env) {
5186 Account const nataly(
"nataly");
5190 {bob, ed, paul, dan, chris, simon, ben, nataly},
5193 for (
int i = 0; i < 10; ++i)
5197 ammAlice.
deposit(simon, USD(0.1));
5199 ammAlice.
deposit(chris, USD(1));
5201 ammAlice.
deposit(dan, USD(10));
5203 ammAlice.
deposit(bob, USD(100));
5205 ammAlice.
deposit(carol, USD(1'000));
5207 ammAlice.
deposit(ed, USD(10'000));
5209 ammAlice.
deposit(paul, USD(100'000));
5211 ammAlice.
deposit(nataly, USD(1'000'000));
5217 if (!features[fixAMMv1_1])
5220 STAmount{USD, UINT64_C(10'000'0000000013), -10},
5225 BEAST_EXPECT(
expectLine(env, ben, USD(1'500'000)));
5226 BEAST_EXPECT(
expectLine(env, simon, USD(1'500'000)));
5227 BEAST_EXPECT(
expectLine(env, chris, USD(1'500'000)));
5228 BEAST_EXPECT(
expectLine(env, dan, USD(1'500'000)));
5229 if (!features[fixAMMv1_1])
5233 STAmount{USD, UINT64_C(30'000'00000000001), -11}));
5235 BEAST_EXPECT(
expectLine(env, carol, USD(30'000)));
5236 BEAST_EXPECT(
expectLine(env, ed, USD(1'500'000)));
5237 BEAST_EXPECT(
expectLine(env, paul, USD(1'500'000)));
5238 if (!features[fixAMMv1_1])
5242 STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
5247 STAmount{USD, UINT64_C(1'500'000'000000005), -9}));
5250 if (!features[fixAMMv1_1])
5254 STAmount{USD, UINT64_C(30'000'0000000013), -10}));
5256 BEAST_EXPECT(
expectLine(env, alice, USD(30'000)));
5262 29950000000 - env.
current()->fees().base.drops()));
5270 testAMM([&](
AMM& ammAlice,
Env& env) {
5278 Account const nataly(
"nataly");
5282 {bob, ed, paul, dan, chris, simon, ben, nataly},
5286 for (
int i = 0; i < 10; ++i)
5313 auto const xrpBalance = (
XRP(2'000'000) -
txfee(env, 20)).getText();
5319 auto const baseFee = env.
current()->fees().base.drops();
5337 testcase(
"Auto Delete");
5339 using namespace jtx;
5350 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5351 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5356 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5361 amm.withdrawAll(gw);
5362 BEAST_EXPECT(amm.ammExists());
5387 env(trust(alice,
STAmount{amm.lptIssue(), 10'000}),
5402 amm.expectBalances(
XRP(10'000), USD(10'000), amm.tokens()));
5403 BEAST_EXPECT(amm.expectTradingFee(1'000));
5404 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
5408 amm.withdrawAll(alice);
5409 BEAST_EXPECT(!amm.ammExists());
5410 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5421 fund(env, gw, {alice},
XRP(20'000), {USD(10'000)});
5422 AMM amm(env, gw,
XRP(10'000), USD(10'000));
5427 env(trust(a,
STAmount{amm.lptIssue(), 10'000}));
5431 amm.withdrawAll(gw);
5432 BEAST_EXPECT(amm.ammExists());
5436 BEAST_EXPECT(amm.ammExists());
5438 amm.ammDelete(alice);
5439 BEAST_EXPECT(!amm.ammExists());
5440 BEAST_EXPECT(!env.
le(keylet::ownerDir(amm.ammAccount())));
5450 testcase(
"Clawback");
5451 using namespace jtx;
5455 AMM amm(env, gw,
XRP(1'000), USD(1'000));
5463 using namespace jtx;
5464 testAMM([&](
AMM& amm,
Env& env) {
5465 amm.setClose(
false);
5466 auto const info = env.
rpc(
5470 "{\"account\": \"" + to_string(amm.ammAccount()) +
"\"}"));
5474 info[jss::result][jss::account_data][jss::AMMID]
5475 .asString() == to_string(amm.ammID()));
5481 amm.deposit(carol, 1'000);
5482 auto affected = env.
meta()->getJson(
5483 JsonOptions::none)[sfAffectedNodes.fieldName];
5487 for (
auto const& node : affected)
5489 if (node.isMember(sfModifiedNode.fieldName) &&
5490 node[sfModifiedNode.fieldName]
5491 [sfLedgerEntryType.fieldName]
5492 .asString() ==
"AccountRoot" &&
5493 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
5495 .asString() == to_string(amm.ammAccount()))
5497 found = node[sfModifiedNode.fieldName]
5498 [sfFinalFields.fieldName][jss::AMMID]
5499 .asString() == to_string(amm.ammID());
5503 BEAST_EXPECT(found);
5515 testcase(
"Offer/Strand Selection");
5516 using namespace jtx;
5519 auto const ETH = gw1[
"ETH"];
5520 auto const CAN = gw1[
"CAN"];
5526 auto prep = [&](
Env& env,
auto gwRate,
auto gw1Rate) {
5527 fund(env, gw, {alice, carol, bob, ed},
XRP(2'000), {USD(2'000)});
5532 {alice, carol, bob, ed},
5533 {ETH(2'000), CAN(2'000)},
5535 env(
rate(gw, gwRate));
5536 env(
rate(gw1, gw1Rate));
5540 for (
auto const& rates :
5555 for (
auto i = 0; i < 3; ++i)
5557 Env env(*
this, features);
5558 prep(env, rates.first, rates.second);
5560 if (i == 0 || i == 2)
5566 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5567 env(pay(carol, bob, USD(100)),
5574 BEAST_EXPECT(amm->expectBalances(
5575 USD(1'000), ETH(1'000), amm->tokens()));
5577 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5578 q[i] = Quality(Amounts{
5579 ETH(2'000) - env.
balance(carol, ETH),
5580 env.
balance(bob, USD) - USD(2'000)});
5583 BEAST_EXPECT(q[0] > q[1]);
5585 BEAST_EXPECT(q[0] == q[2]);
5592 for (
auto i = 0; i < 3; ++i)
5594 Env env(*
this, features);
5595 prep(env, rates.first, rates.second);
5597 if (i == 0 || i == 2)
5603 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5604 env(offer(alice, USD(400), ETH(400)));
5609 BEAST_EXPECT(amm->expectBalances(
5610 USD(1'000), ETH(1'000), amm->tokens()));
5612 if (i == 0 || i == 2)
5621 env, alice, 1, {Amounts{USD(400), ETH(400)}}));
5632 for (
auto i = 0; i < 3; ++i)
5634 Env env(*
this, features);
5635 prep(env, rates.first, rates.second);
5637 if (i == 0 || i == 2)
5643 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5644 env(pay(carol, bob, USD(100)),
5651 BEAST_EXPECT(!amm->expectBalances(
5652 USD(1'000), ETH(1'000), amm->tokens()));
5654 if (i == 2 && !features[fixAMMv1_1])
5656 if (rates.first == 1.5)
5658 if (!features[fixAMMv1_1])
5666 UINT64_C(378'6327949540823),
5670 UINT64_C(283'9745962155617),
5680 UINT64_C(378'6327949540813),
5684 UINT64_C(283'974596215561),
5689 if (!features[fixAMMv1_1])
5697 UINT64_C(325'299461620749),
5701 UINT64_C(243'9745962155617),
5711 UINT64_C(325'299461620748),
5715 UINT64_C(243'974596215561),
5721 if (rates.first == 1.5)
5729 ETH, UINT64_C(378'6327949540812), -13},
5732 UINT64_C(283'9745962155609),
5743 ETH, UINT64_C(325'2994616207479), -13},
5746 UINT64_C(243'9745962155609),
5750 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5751 q[i] = Quality(Amounts{
5752 ETH(2'000) - env.
balance(carol, ETH),
5753 env.
balance(bob, USD) - USD(2'000)});
5756 BEAST_EXPECT(q[1] > q[0]);
5758 BEAST_EXPECT(q[2] > q[1]);
5762 for (
auto i = 0; i < 3; ++i)
5764 Env env(*
this, features);
5765 prep(env, rates.first, rates.second);
5767 if (i == 0 || i == 2)
5773 amm.emplace(env, ed, USD(1'000), ETH(1'000));
5774 env(offer(alice, USD(250), ETH(400)));
5779 BEAST_EXPECT(!amm->expectBalances(
5780 USD(1'000), ETH(1'000), amm->tokens()));
5786 if (rates.first == 1.5)
5788 if (!features[fixAMMv1_1])
5791 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
5798 USD, UINT64_C(40'5694150420947), -13},
5800 ETH, UINT64_C(64'91106406735152), -14},
5814 ETH, UINT64_C(335'0889359326475), -13},
5816 USD, UINT64_C(209'4305849579047), -13},
5823 if (!features[fixAMMv1_1])
5832 ETH, UINT64_C(335'0889359326485), -13},
5834 USD, UINT64_C(209'4305849579053), -13},
5847 ETH, UINT64_C(335'0889359326475), -13},
5849 USD, UINT64_C(209'4305849579047), -13},
5875 for (
auto i = 0; i < 3; ++i)
5877 Env env(*
this, features);
5878 prep(env, rates.first, rates.second);
5881 if (i == 0 || i == 2)
5889 amm.emplace(env, ed, ETH(1'000), USD(1'000));
5891 env(pay(carol, bob, USD(100)),
5897 BEAST_EXPECT(
expectLine(env, bob, USD(2'100)));
5899 if (i == 2 && !features[fixAMMv1_1])
5901 if (rates.first == 1.5)
5904 BEAST_EXPECT(amm->expectBalances(
5905 STAmount{ETH, UINT64_C(1'176'66038955758), -11},
5911 BEAST_EXPECT(amm->expectBalances(
5913 ETH, UINT64_C(1'179'540094339627), -12},
5914 STAmount{USD, UINT64_C(847'7880529867501), -13},
5923 UINT64_C(343'3179205198749),
5927 UINT64_C(343'3179205198749),
5933 UINT64_C(362'2119470132499),
5937 UINT64_C(362'2119470132499),
5944 if (rates.first == 1.5)
5947 BEAST_EXPECT(amm->expectBalances(
5949 ETH, UINT64_C(1'176'660389557593), -12},
5955 BEAST_EXPECT(amm->expectBalances(
5956 STAmount{ETH, UINT64_C(1'179'54009433964), -11},
5957 STAmount{USD, UINT64_C(847'7880529867501), -13},
5966 UINT64_C(343'3179205198749),
5970 UINT64_C(343'3179205198749),
5976 UINT64_C(362'2119470132499),
5980 UINT64_C(362'2119470132499),
5985 q[i] = Quality(Amounts{
5986 ETH(2'000) - env.
balance(carol, ETH),
5987 env.
balance(bob, USD) - USD(2'000)});
5989 BEAST_EXPECT(q[1] > q[0]);
5990 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
5998 testcase(
"Fix Default Inner Object");
5999 using namespace jtx;
6010 Env env(*
this, features);
6011 fund(env, gw, {alice},
XRP(1'000), {USD(10)});
6017 {.tfee = tfee, .close = closeLedger});
6018 amm.deposit(alice, USD(10),
XRP(10));
6021 .
account = gw, .asset1Out = USD(1), .err =
ter(err2)});
6029 .
account = gw, .asset1Out = USD(2), .err =
ter(err4)});
6036 all - fixInnerObjTemplate,
6051 all - fixInnerObjTemplate,
6063 all - fixInnerObjTemplate,
6072 all - fixInnerObjTemplate,
6086 all - fixInnerObjTemplate,
6098 testcase(
"Fix changeSpotPriceQuality");
6099 using namespace jtx;
6104 SucceedShouldSucceedResize,
6116 auto const xrpIouAmounts10_100 =
6118 auto const iouXrpAmounts10_100 =
6123 {
"0.001519763260828713",
"1558701", Quality{5414253689393440221}, 1000, FailShouldSucceed},
6124 {
"0.01099814367603737",
"1892611", Quality{5482264816516900274}, 1000, FailShouldSucceed},
6125 {
"0.78",
"796599", Quality{5630392334958379008}, 1000, FailShouldSucceed},
6126 {
"105439.2955578965",
"49398693", Quality{5910869983721805038}, 400, FailShouldSucceed},
6127 {
"12408293.23445213",
"4340810521", Quality{5911611095910090752}, 997, FailShouldSucceed},
6128 {
"1892611",
"0.01099814367603737", Quality{6703103457950430139}, 1000, FailShouldSucceed},
6129 {
"423028.8508101858",
"3392804520", Quality{5837920340654162816}, 600, FailShouldSucceed},
6130 {
"44565388.41001027",
"73890647", Quality{6058976634606450001}, 1000, FailShouldSucceed},
6131 {
"66831.68494832662",
"16", Quality{6346111134641742975}, 0, FailShouldSucceed},
6132 {
"675.9287302203422",
"1242632304", Quality{5625960929244093294}, 300, FailShouldSucceed},
6133 {
"7047.112186735699",
"1649845866", Quality{5696855348026306945}, 504, FailShouldSucceed},
6134 {
"840236.4402981238",
"47419053", Quality{5982561601648018688}, 499, FailShouldSucceed},
6135 {
"992715.618909774",
"189445631733", Quality{5697835648288106944}, 815, SucceedShouldSucceedResize},
6136 {
"504636667521",
"185545883.9506651", Quality{6343802275337659280}, 503, SucceedShouldSucceedResize},
6137 {
"992706.7218636649",
"189447316000", Quality{5697835648288106944}, 797, SucceedShouldSucceedResize},
6138 {
"1.068737911388205",
"127860278877", Quality{5268604356368739396}, 293, SucceedShouldSucceedResize},
6139 {
"17932506.56880419",
"189308.6043676173", Quality{6206460598195440068}, 311, SucceedShouldSucceedResize},
6140 {
"1.066379294658174",
"128042251493", Quality{5268559341368739328}, 270, SucceedShouldSucceedResize},
6141 {
"350131413924",
"1576879.110907892", Quality{6487411636539049449}, 650,
Fail},
6142 {
"422093460",
"2.731797662057464", Quality{6702911108534394924}, 1000,
Fail},
6143 {
"76128132223",
"367172.7148422662", Quality{6487263463413514240}, 548,
Fail},
6144 {
"132701839250",
"280703770.7695443", Quality{6273750681188885075}, 562,
Fail},
6145 {
"994165.7604612011",
"189551302411", Quality{5697835592690668727}, 815,
Fail},
6146 {
"45053.33303227917",
"86612695359", Quality{5625695218943638190}, 500,
Fail},
6147 {
"199649.077043865",
"14017933007", Quality{5766034667318524880}, 324,
Fail},
6148 {
"27751824831.70903",
"78896950", Quality{6272538159621630432}, 500,
Fail},
6149 {
"225.3731275781907",
"156431793648", Quality{5477818047604078924}, 989,
Fail},
6150 {
"199649.077043865",
"14017933007", Quality{5766036094462806309}, 324,
Fail},
6151 {
"3.590272027140361",
"20677643641", Quality{5406056147042156356}, 808,
Fail},
6152 {
"1.070884664490231",
"127604712776", Quality{5268620608623825741}, 293,
Fail},
6153 {
"3272.448829820197",
"6275124076", Quality{5625710328924117902}, 81,
Fail},
6154 {
"0.009059512633902926",
"7994028", Quality{5477511954775533172}, 1000,
Fail},
6155 {
"1",
"1.0", Quality{0}, 100,
Fail},
6156 {
"1.0",
"1", Quality{0}, 100,
Fail},
6157 {
"10",
"10.0", Quality{xrpIouAmounts10_100}, 100,
Fail},
6158 {
"10.0",
"10", Quality{iouXrpAmounts10_100}, 100,
Fail},
6159 {
"69864389131",
"287631.4543025075", Quality{6487623473313516078}, 451, Succeed},
6160 {
"4328342973",
"12453825.99247381", Quality{6272522264364865181}, 997, Succeed},
6161 {
"32347017",
"7003.93031579449", Quality{6347261126087916670}, 1000, Succeed},
6162 {
"61697206161",
"36631.4583206413", Quality{6558965195382476659}, 500, Succeed},
6163 {
"1654524979",
"7028.659825511603", Quality{6487551345110052981}, 504, Succeed},
6164 {
"88621.22277293179",
"5128418948", Quality{5766347291552869205}, 380, Succeed},
6165 {
"1892611",
"0.01099814367603737", Quality{6703102780512015436}, 1000, Succeed},
6166 {
"4542.639373338766",
"24554809", Quality{5838994982188783710}, 0, Succeed},
6167 {
"5132932546",
"88542.99750172683", Quality{6419203342950054537}, 380, Succeed},
6168 {
"78929964.1549083",
"1506494795", Quality{5986890029845558688}, 589, Succeed},
6169 {
"10096561906",
"44727.72453735605", Quality{6487455290284644551}, 250, Succeed},
6170 {
"5092.219565514988",
"8768257694", Quality{5626349534958379008}, 503, Succeed},
6171 {
"1819778294",
"8305.084302902864", Quality{6487429398998540860}, 415, Succeed},
6172 {
"6970462.633911943",
"57359281", Quality{6054087899185946624}, 850, Succeed},
6173 {
"3983448845",
"2347.543644281467", Quality{6558965195382476659}, 856, Succeed},
6177 {
"771493171",
"1.243473020567508", Quality{6707566798038544272}, 100, SucceedShouldFail},
6181 boost::regex rx(
"^\\d+$");
6182 boost::smatch match;
6185 Env env(*
this, features, std::make_unique<CaptureLogs>(&logs));
6186 auto rules = env.
current()->rules();
6188 for (
auto const& t : tests)
6195 auto const& quality = std::get<Quality>(t);
6196 auto const tfee = std::get<std::uint16_t>(t);
6197 auto const status = std::get<Status>(t);
6198 auto const poolInIsXRP =
6199 boost::regex_search(std::get<0>(t), match, rx);
6200 auto const poolOutIsXRP =
6201 boost::regex_search(std::get<1>(t), match, rx);
6202 assert(!(poolInIsXRP && poolOutIsXRP));
6203 auto const poolIn = getPool(std::get<0>(t), poolInIsXRP);
6204 auto const poolOut = getPool(std::get<1>(t), poolOutIsXRP);
6208 Amounts{poolIn, poolOut},
6215 if (status == SucceedShouldSucceedResize)
6217 if (!features[fixAMMv1_1])
6218 BEAST_EXPECT(Quality{*amounts} < quality);
6220 BEAST_EXPECT(Quality{*amounts} >= quality);
6222 else if (status == Succeed)
6224 if (!features[fixAMMv1_1])
6226 Quality{*amounts} >= quality ||
6228 Quality{*amounts}, quality,
Number{1, -7}));
6230 BEAST_EXPECT(Quality{*amounts} >= quality);
6232 else if (status == FailShouldSucceed)
6235 features[fixAMMv1_1] &&
6236 Quality{*amounts} >= quality);
6238 else if (status == SucceedShouldFail)
6241 !features[fixAMMv1_1] &&
6242 Quality{*amounts} < quality &&
6244 Quality{*amounts}, quality,
Number{1, -7}));
6253 if (status ==
Fail && quality != Quality{0})
6255 auto tinyOffer = [&]() {
6262 Amounts{poolIn, poolOut},
6266 else if (
isXRP(poolOut))
6271 Amounts{poolIn, poolOut},
6276 auto const takerPays = toAmount<STAmount>(
6281 Amounts{poolIn, poolOut}, takerPays, tfee)};
6283 BEAST_EXPECT(Quality(tinyOffer) < quality);
6285 else if (status == FailShouldSucceed)
6287 BEAST_EXPECT(!features[fixAMMv1_1]);
6289 else if (status == SucceedShouldFail)
6291 BEAST_EXPECT(features[fixAMMv1_1]);
6298 !strcmp(e.
what(),
"changeSpotPriceQuality failed"));
6300 !features[fixAMMv1_1] && status == FailShouldSucceed);
6309 BEAST_EXPECT(!res.has_value());
6316 using namespace jtx;
6318 testAMM([&](
AMM& ammAlice,
Env& env) {
6326 testAMM([&](
AMM& ammAlice,
Env& env) {
6334 testAMM([&](
AMM& ammAlice,
Env& env) {
6342 testAMM([&](
AMM& ammAlice,
Env& env) {
6345 .asset2Out =
XRP(100),
6351 testAMM([&](
AMM& ammAlice,
Env& env) {
6354 .asset2Out = BAD(100),
6360 testAMM([&](
AMM& ammAlice,
Env& env) {
6362 jv[jss::TransactionType] = jss::AMMWithdraw;
6364 jv[jss::Account] = alice.human();
6366 XRP(100).value().setJson(jv[jss::Amount]);
6367 USD(100).value().setJson(jv[jss::EPrice]);
6375 using namespace jtx;
6381 Account const gatehub{
"gatehub"};
6382 Account const bitstamp{
"bitstamp"};
6383 Account const trader{
"trader"};
6384 auto const usdGH = gatehub[
"USD"];
6385 auto const btcGH = gatehub[
"BTC"];
6386 auto const usdBIT = bitstamp[
"USD"];
6390 char const* testCase;
6391 double const poolUsdBIT;
6392 double const poolUsdGH;
6404 double const offer1BtcGH = 0.1;
6405 double const offer2BtcGH = 0.1;
6406 double const offer2UsdGH = 1;
6407 double const rateBIT = 0.0;
6408 double const rateGH = 0.0;
6413 for (
auto const& input : {
6415 .testCase =
"Test Fix Overflow Offer",
6418 .sendMaxUsdBIT{usdBIT(50)},
6419 .sendUsdGH{usdGH,
uint64_t(272'455089820359), -12},
6422 .failUsdBIT{usdBIT,
uint64_t(46'47826086956522), -14},
6423 .failUsdBITr{usdBIT,
uint64_t(46'47826086956521), -14},
6424 .goodUsdGH{usdGH,
uint64_t(96'7543114220382), -13},
6425 .goodUsdGHr{usdGH,
uint64_t(96'7543114222965), -13},
6426 .goodUsdBIT{usdBIT,
uint64_t(8'464739069120721), -15},
6427 .goodUsdBITr{usdBIT,
uint64_t(8'464739069098152), -15},
6428 .lpTokenBalance = {28'61817604250837, -14},
6436 .testCase =
"Overflow test {1, 100, 0.111}",
6439 .sendMaxUsdBIT{usdBIT(0.111)},
6440 .sendUsdGH{usdGH, 100},
6443 .failUsdBIT{usdBIT,
uint64_t(1'111), -3},
6444 .failUsdBITr{usdBIT,
uint64_t(1'111), -3},
6445 .goodUsdGH{usdGH,
uint64_t(90'04347888284115), -14},
6446 .goodUsdGHr{usdGH,
uint64_t(90'04347888284201), -14},
6447 .goodUsdBIT{usdBIT,
uint64_t(1'111), -3},
6448 .goodUsdBITr{usdBIT,
uint64_t(1'111), -3},
6449 .lpTokenBalance{10, 0},
6450 .offer1BtcGH = 1e-5,
6452 .offer2UsdGH = 1e-5,
6457 .testCase =
"Overflow test {1, 100, 1.00}",
6460 .sendMaxUsdBIT{usdBIT(1.00)},
6461 .sendUsdGH{usdGH, 100},
6464 .failUsdBIT{usdBIT,
uint64_t(2), 0},
6465 .failUsdBITr{usdBIT,
uint64_t(2), 0},
6466 .goodUsdGH{usdGH,
uint64_t(52'94379354424079), -14},
6467 .goodUsdGHr{usdGH,
uint64_t(52'94379354424135), -14},
6468 .goodUsdBIT{usdBIT,
uint64_t(2), 0},
6469 .goodUsdBITr{usdBIT,
uint64_t(2), 0},
6470 .lpTokenBalance{10, 0},
6471 .offer1BtcGH = 1e-5,
6473 .offer2UsdGH = 1e-5,
6478 .testCase =
"Overflow test {1, 100, 4.6432}",
6481 .sendMaxUsdBIT{usdBIT(4.6432)},
6482 .sendUsdGH{usdGH, 100},
6485 .failUsdBIT{usdBIT,
uint64_t(5'6432), -4},
6486 .failUsdBITr{usdBIT,
uint64_t(5'6432), -4},
6487 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6488 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6489 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6490 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6491 .lpTokenBalance{10, 0},
6492 .offer1BtcGH = 1e-5,
6494 .offer2UsdGH = 1e-5,
6499 .testCase =
"Overflow test {1, 100, 10}",
6502 .sendMaxUsdBIT{usdBIT(10)},
6503 .sendUsdGH{usdGH, 100},
6506 .failUsdBIT{usdBIT,
uint64_t(11), 0},
6507 .failUsdBITr{usdBIT,
uint64_t(11), 0},
6508 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6509 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6510 .goodUsdBIT{usdBIT,
uint64_t(2'821579689703915), -15},
6511 .goodUsdBITr{usdBIT,
uint64_t(2'821579689703954), -15},
6512 .lpTokenBalance{10, 0},
6513 .offer1BtcGH = 1e-5,
6515 .offer2UsdGH = 1e-5,
6520 .testCase =
"Overflow test {50, 100, 5.55}",
6523 .sendMaxUsdBIT{usdBIT(5.55)},
6524 .sendUsdGH{usdGH, 100},
6527 .failUsdBIT{usdBIT,
uint64_t(55'55), -2},
6528 .failUsdBITr{usdBIT,
uint64_t(55'55), -2},
6529 .goodUsdGH{usdGH,
uint64_t(90'04347888284113), -14},
6530 .goodUsdGHr{usdGH,
uint64_t(90'0434788828413), -13},
6531 .goodUsdBIT{usdBIT,
uint64_t(55'55), -2},
6532 .goodUsdBITr{usdBIT,
uint64_t(55'55), -2},
6533 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6534 .offer1BtcGH = 1e-5,
6536 .offer2UsdGH = 1e-5,
6541 .testCase =
"Overflow test {50, 100, 50.00}",
6544 .sendMaxUsdBIT{usdBIT(50.00)},
6545 .sendUsdGH{usdGH, 100},
6546 .failUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6547 .failUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6548 .failUsdBIT{usdBIT,
uint64_t(100), 0},
6549 .failUsdBITr{usdBIT,
uint64_t(100), 0},
6550 .goodUsdGH{usdGH,
uint64_t(52'94379354424081), -14},
6551 .goodUsdGHr{usdGH,
uint64_t(52'94379354424092), -14},
6552 .goodUsdBIT{usdBIT,
uint64_t(100), 0},
6553 .goodUsdBITr{usdBIT,
uint64_t(100), 0},
6554 .lpTokenBalance{
uint64_t(70'71067811865475), -14},
6555 .offer1BtcGH = 1e-5,
6557 .offer2UsdGH = 1e-5,
6562 .testCase =
"Overflow test {50, 100, 232.16}",
6565 .sendMaxUsdBIT{usdBIT(232.16)},
6566 .sendUsdGH{usdGH, 100},
6569 .failUsdBIT{usdBIT,
uint64_t(282'16), -2},
6570 .failUsdBITr{usdBIT,
uint64_t(282'16), -2},
6571 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6572 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6573 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6574 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6575 .lpTokenBalance{70'71067811865475, -14},
6576 .offer1BtcGH = 1e-5,
6578 .offer2UsdGH = 1e-5,
6583 .testCase =
"Overflow test {50, 100, 500}",
6586 .sendMaxUsdBIT{usdBIT(500)},
6587 .sendUsdGH{usdGH, 100},
6590 .failUsdBIT{usdBIT,
uint64_t(550), 0},
6591 .failUsdBITr{usdBIT,
uint64_t(550), 0},
6592 .goodUsdGH{usdGH,
uint64_t(35'44113971506987), -14},
6593 .goodUsdGHr{usdGH,
uint64_t(35'44113971506987), -14},
6594 .goodUsdBIT{usdBIT,
uint64_t(141'0789844851958), -13},
6595 .goodUsdBITr{usdBIT,
uint64_t(141'0789844851962), -13},
6596 .lpTokenBalance{70'71067811865475, -14},
6597 .offer1BtcGH = 1e-5,
6599 .offer2UsdGH = 1e-5,
6605 testcase(input.testCase);
6606 for (
auto const& features :
6607 {all - fixAMMOverflowOffer, all | fixAMMOverflowOffer})
6609 Env env(*
this, features, std::make_unique<CaptureLogs>(&logs));
6611 env.
fund(
XRP(5'000), gatehub, bitstamp, trader);
6614 if (input.rateGH != 0.0)
6615 env(
rate(gatehub, input.rateGH));
6616 if (input.rateBIT != 0.0)
6617 env(
rate(bitstamp, input.rateBIT));
6619 env(trust(trader, usdGH(10'000'000)));
6620 env(trust(trader, usdBIT(10'000'000)));
6621 env(trust(trader, btcGH(10'000'000)));
6624 env(pay(gatehub, trader, usdGH(100'000)));
6625 env(pay(gatehub, trader, btcGH(100'000)));
6626 env(pay(bitstamp, trader, usdBIT(100'000)));
6632 usdGH(input.poolUsdGH),
6633 usdBIT(input.poolUsdBIT)};
6637 amm.getLPTokensBalance();
6639 env(offer(trader, usdBIT(1), btcGH(input.offer1BtcGH)));
6642 btcGH(input.offer2BtcGH),
6643 usdGH(input.offer2UsdGH)));
6646 env(pay(trader, trader, input.sendUsdGH),
6648 path(~btcGH, ~usdGH),
6653 auto const failUsdGH =
6654 features[fixAMMv1_1] ? input.failUsdGHr : input.failUsdGH;
6655 auto const failUsdBIT =
6656 features[fixAMMv1_1] ? input.failUsdBITr : input.failUsdBIT;
6657 auto const goodUsdGH =
6658 features[fixAMMv1_1] ? input.goodUsdGHr : input.goodUsdGH;
6659 auto const goodUsdBIT =
6660 features[fixAMMv1_1] ? input.goodUsdBITr : input.goodUsdBIT;
6661 if (!features[fixAMMOverflowOffer])
6663 BEAST_EXPECT(amm.expectBalances(
6664 failUsdGH, failUsdBIT, input.lpTokenBalance));
6668 BEAST_EXPECT(amm.expectBalances(
6669 goodUsdGH, goodUsdBIT, input.lpTokenBalance));
6674 amm.getLPTokensBalance() == preSwapLPTokenBalance);
6678 Number const sqrtPoolProduct =
6679 root2(goodUsdGH * goodUsdBIT);
6690 (sqrtPoolProduct +
Number{1, -14} >=
6691 input.lpTokenBalance));
6700 testcase(
"swapRounding");
6701 using namespace jtx;
6703 STAmount const xrpPool{
XRP, UINT64_C(51600'000981)};
6704 STAmount const iouPool{USD, UINT64_C(803040'9987141784), -10};
6706 STAmount const xrpBob{
XRP, UINT64_C(1092'878933)};
6708 USD, UINT64_C(3'988035892323031), -28};
6711 [&](
AMM& amm,
Env& env) {
6713 auto [xrpBegin, iouBegin, lptBegin] = amm.balances(
XRP, USD);
6716 env.
fund(xrpBob, bob);
6717 env.
trust(USD(1'000'000), bob);
6718 env(pay(gw, bob, iouBob));
6721 env(offer(bob,
XRP(6300), USD(100'000)));
6726 amm.expectBalances(xrpBegin, iouBegin, amm.tokens()));
6728 {{xrpPool, iouPool}},
6731 {jtx::supported_amendments() | fixAMMv1_1});
6737 testcase(
"AMM Offer Blocked By LOB");
6738 using namespace jtx;
6744 Env env(*
this, features);
6746 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6748 env(offer(alice,
XRP(1), USD(0.01)));
6751 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6755 env(offer(carol, USD(0.49),
XRP(1)));
6758 if (!features[fixAMMv1_1])
6760 BEAST_EXPECT(amm.expectBalances(
6761 XRP(200'000), USD(100'000), amm.tokens()));
6763 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6766 env, carol, 1, {{Amounts{USD(0.49),
XRP(1)}}}));
6770 BEAST_EXPECT(amm.expectBalances(
6771 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6773 env, alice, 1, {{Amounts{
XRP(1), USD(0.01)}}}));
6782 Env env(*
this, features);
6784 fund(env, gw, {alice, carol},
XRP(1'000'000), {USD(1'000'000)});
6788 AMM amm(env, gw,
XRP(200'000), USD(100'000));
6791 env(offer(carol, USD(0.49),
XRP(1)));
6795 BEAST_EXPECT(amm.expectBalances(
6796 XRPAmount(200'000'980'005), USD(99'999.51), amm.tokens()));
6803 Env env(*
this, features);
6804 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6808 env(offer(bob, USD(1),
XRPAmount(500)));
6810 AMM amm(env, alice,
XRP(1'000), USD(500));
6811 env(offer(carol,
XRP(100), USD(55)));
6813 if (!features[fixAMMv1_1])
6816 amm.expectBalances(
XRP(1'000), USD(500), amm.tokens()));
6818 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6820 env, carol, 1, {{Amounts{
XRP(100), USD(55)}}}));
6824 BEAST_EXPECT(amm.expectBalances(
6826 STAmount{USD, UINT64_C(550'000000055), -9},
6834 STAmount{USD, 4'99999995, -8}}}}));
6836 env, bob, 1, {{Amounts{USD(1),
XRPAmount(500)}}}));
6843 Env env(*
this, features);
6844 fund(env, gw, {alice, carol, bob},
XRP(10'000), {USD(1'000)});
6846 AMM amm(env, alice,
XRP(1'000), USD(500));
6847 env(offer(carol,
XRP(100), USD(55)));
6849 BEAST_EXPECT(amm.expectBalances(
6851 STAmount{USD, UINT64_C(550'000000055), -9},
6865 using namespace jtx;
6869 Env env(*
this, features);
6875 {USD(1'000'000'000)});
6876 AMM amm(env, gw,
XRP(2), USD(1));
6877 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6878 amm.deposit(carol,
IOUAmount{1'000'000});
6879 amm.withdrawAll(alice);
6880 amm.withdrawAll(carol);
6882 env, gw, amm.lptIssue())[jss::lines][0u][jss::balance];
6883 auto const lpTokenBalance =
6884 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6886 lpToken ==
"1414.213562373095" &&
6887 lpTokenBalance ==
"1414.213562373");
6888 if (!features[fixAMMv1_1])
6891 BEAST_EXPECT(amm.ammExists());
6895 amm.withdrawAll(gw);
6896 BEAST_EXPECT(!amm.ammExists());
6902 for (
auto const& lp : {gw, bob})
6904 Env env(*
this, features);
6905 auto const ABC = gw[
"ABC"];
6909 {alice, carol, bob},
6911 {USD(1'000'000'000), ABC(1'000'000'000'000)});
6912 AMM amm(env, lp, ABC(2'000'000), USD(1));
6913 amm.deposit(alice,
IOUAmount{1'876123487565916, -15});
6914 amm.deposit(carol,
IOUAmount{1'000'000});
6915 amm.withdrawAll(alice);
6916 amm.withdrawAll(carol);
6918 env, lp, amm.lptIssue())[jss::lines][0u][jss::balance];
6919 auto const lpTokenBalance =
6920 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value];
6922 lpToken ==
"1414.213562373095" &&
6923 lpTokenBalance ==
"1414.213562373");
6924 if (!features[fixAMMv1_1])
6927 BEAST_EXPECT(amm.ammExists());
6931 amm.withdrawAll(lp);
6932 BEAST_EXPECT(!amm.ammExists());
6939 Env env(*
this, features);
6940 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)});
6941 AMM amm(env, gw,
XRP(10), USD(10));
6942 amm.deposit(alice, 1'000);
6945 BEAST_EXPECT(res && !res.value());
6948 BEAST_EXPECT(res && !res.value());
6952 Env env(*
this, features);
6953 fund(env, gw, {alice},
XRP(1'000), {USD(1'000), EUR(1'000)});
6954 AMM amm(env, gw, EUR(10), USD(10));
6955 amm.deposit(alice, 1'000);
6958 BEAST_EXPECT(res && !res.value());
6961 BEAST_EXPECT(res && !res.value());
6965 Env env(*
this, features);
6967 auto const YAN = gw1[
"YAN"];
6968 fund(env, gw, {gw1},
XRP(1'000), {USD(1'000)});
6969 fund(env, gw1, {gw},
XRP(1'000), {YAN(1'000)}, Fund::IOUOnly);
6970 AMM amm(env, gw1, YAN(10), USD(10));
6971 amm.deposit(gw, 1'000);
6974 BEAST_EXPECT(res && !res.value());
6976 BEAST_EXPECT(res && !res.value());
6983 testcase(
"test clawback from AMM account");
6984 using namespace jtx;
6987 Env env(*
this, features);
6990 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
6995 if (!features[featureAMMClawback])
7021 Issue usd(USD.issue().currency, amm.ammAccount());
7030 testcase(
"test AMMDeposit with frozen assets");
7031 using namespace jtx;
7038 fund(env, gw, {alice},
XRP(1'000), {USD(1'000)}, Fund::Acct);
7048 Env env(*
this, features);
7049 testAMMDeposit(env, [&](
AMM& amm) {
7063 Env env(*
this, features);
7064 testAMMDeposit(env, [&](
AMM& amm) {
7075 if (features[featureAMMClawback])
7080 Env env(*
this, features);
7081 testAMMDeposit(env, [&](
AMM& amm) {
7096 Env env(*
this, features);
7097 testAMMDeposit(env, [&](
AMM& amm) {
7112 testcase(
"Fix Reserve Check On Withdrawal");
7113 using namespace jtx;
7118 auto test = [&](
auto&& cb) {
7119 Env env(*
this, features);
7120 auto const starting_xrp =
7121 reserve(env, 2) + env.
current()->fees().base * 5;
7122 env.
fund(starting_xrp, gw);
7123 env.
fund(starting_xrp, alice);
7124 env.
trust(USD(2'000), alice);
7126 env(pay(gw, alice, USD(2'000)));
7128 AMM amm(env, gw, EUR(1'000), USD(1'000));
7129 amm.deposit(alice, USD(1));
7134 test([&](
AMM& amm) { amm.withdrawAll(alice, std::nullopt, err); });
7137 test([&](
AMM& amm) {
7140 .asset1Out = EUR(0.1),
7141 .asset2Out = USD(0.1),
7145 .asset1Out = USD(0.1),
7146 .asset2Out = EUR(0.1),
7151 test([&](
AMM& amm) {
7153 .
account = alice, .asset1Out = EUR(0.1), .err = err});
7161 using namespace test::jtx;
7164 testcase(
"Failed pseudo-account allocation " + suffix);
7165 Env env{*
this, features};
7166 env.
fund(
XRP(30'000), gw, alice);
7168 env(trust(alice, gw[
"USD"](30'000), 0));
7169 env(pay(gw, alice, USD(10'000)));
7174 auto const keylet = keylet::amm(amount.
issue(), amount2.
issue());
7175 for (
int i = 0; i < 256; ++i)
7198 "terADDRESS_COLLISION",
7206 testInvalidInstance();
7207 testInstanceCreate();
7208 testInvalidDeposit(all);
7209 testInvalidDeposit(all - featureAMMClawback);
7211 testInvalidWithdraw();
7213 testInvalidFeeVote();
7217 testBid(all - fixAMMv1_1);
7218 testInvalidAMMPayment();
7219 testBasicPaymentEngine(all);
7220 testBasicPaymentEngine(all - fixAMMv1_1);
7221 testBasicPaymentEngine(all - fixReducedOffersV2);
7222 testBasicPaymentEngine(all - fixAMMv1_1 - fixReducedOffersV2);
7227 testAMMAndCLOB(all);
7228 testAMMAndCLOB(all - fixAMMv1_1);
7229 testTradingFee(all);
7230 testTradingFee(all - fixAMMv1_1);
7231 testAdjustedTokens(all);
7232 testAdjustedTokens(all - fixAMMv1_1);
7237 testSelection(all - fixAMMv1_1);
7238 testFixDefaultInnerObj();
7240 testFixOverflowOffer(all);
7241 testFixOverflowOffer(all - fixAMMv1_1);
7243 testFixChangeSpotPriceQuality(all);
7244 testFixChangeSpotPriceQuality(all - fixAMMv1_1);
7245 testFixAMMOfferBlockedByLOB(all);
7246 testFixAMMOfferBlockedByLOB(all - fixAMMv1_1);
7247 testLPTokenBalance(all);
7248 testLPTokenBalance(all - fixAMMv1_1);
7249 testAMMClawback(all);
7250 testAMMClawback(all - featureAMMClawback);
7251 testAMMClawback(all - fixAMMv1_1 - featureAMMClawback);
7252 testAMMDepositWithFrozenAssets(all);
7253 testAMMDepositWithFrozenAssets(all - featureAMMClawback);
7254 testAMMDepositWithFrozenAssets(all - fixAMMv1_1 - featureAMMClawback);
7255 testFixReserveCheckOnWithdrawal(all);
7256 testFixReserveCheckOnWithdrawal(all - fixAMMv1_2);
7257 testFailedPseudoAccount();
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
testcase_t testcase
Memberspace for declaring test cases.
virtual Config & config()=0
std::unordered_set< uint256, beast::uhash<> > features
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.
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
NetClock::time_point now()
Returns the current network time.
std::shared_ptr< STTx const > ust(JTx const &jt)
Create a STTx from a JTx without sanitizing Use to inject bogus values into test transactions by firs...
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.
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.
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
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
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 testFailedPseudoAccount()
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.