xrpld
Loading...
Searching...
No Matches
AMMExtendedMPT_test.cpp
1#include <test/jtx/AMM.h>
2#include <test/jtx/AMMTest.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/PathSet.h>
5#include <test/jtx/TestHelpers.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/balance.h>
8#include <test/jtx/delivermin.h>
9#include <test/jtx/fee.h>
10#include <test/jtx/flags.h>
11#include <test/jtx/jtx_json.h>
12#include <test/jtx/mpt.h>
13#include <test/jtx/multisign.h>
14#include <test/jtx/noop.h>
15#include <test/jtx/offer.h>
16#include <test/jtx/owners.h>
17#include <test/jtx/paths.h>
18#include <test/jtx/pay.h>
19#include <test/jtx/regkey.h>
20#include <test/jtx/require.h>
21#include <test/jtx/sendmax.h>
22#include <test/jtx/sig.h>
23#include <test/jtx/tags.h>
24#include <test/jtx/ter.h>
25#include <test/jtx/txflags.h>
26
27#include <xrpl/beast/unit_test/suite.h>
28#include <xrpl/json/json_value.h>
29#include <xrpl/json/to_string.h>
30#include <xrpl/ledger/ApplyView.h>
31#include <xrpl/ledger/OpenView.h>
32#include <xrpl/ledger/PaymentSandbox.h>
33#include <xrpl/ledger/Sandbox.h>
34#include <xrpl/ledger/helpers/OfferHelpers.h>
35#include <xrpl/protocol/AccountID.h>
36#include <xrpl/protocol/Feature.h>
37#include <xrpl/protocol/Indexes.h>
38#include <xrpl/protocol/Issue.h>
39#include <xrpl/protocol/KeyType.h>
40#include <xrpl/protocol/Quality.h>
41#include <xrpl/protocol/SField.h>
42#include <xrpl/protocol/STPathSet.h>
43#include <xrpl/protocol/Seed.h>
44#include <xrpl/protocol/TER.h>
45#include <xrpl/protocol/TxFlags.h>
46#include <xrpl/protocol/UintTypes.h>
47#include <xrpl/protocol/XRPAmount.h>
48#include <xrpl/protocol/jss.h>
49#include <xrpl/tx/paths/Flow.h>
50#include <xrpl/tx/paths/detail/Steps.h>
51
52#include <cstdint>
53#include <optional>
54#include <tuple>
55
56namespace xrpl::test {
57
62{
63private:
64 void
66 {
67 testcase("Incorrect Removal of Funded Offers");
68
69 // We need at least two paths. One at good quality and one at bad
70 // quality. The bad quality path needs two offer books in a row.
71 // Each offer book should have two offers at the same quality, the
72 // offers should be completely consumed, and the payment should
73 // require both offers to be satisfied. The first offer must
74 // be "taker gets" XRP. Ensure that the payment engine does not remove
75 // the first "taker gets" xrp offer, because the offer is still
76 // funded and not used for the payment.
77
78 using namespace jtx;
79 Env env{*this, features};
80
81 fund(env, gw_, {alice_, bob_, carol_}, XRP(10'000));
82
83 MPTTester const eth(
84 {.env = env,
85 .issuer = gw_,
86 .holders = {alice_, bob_, carol_},
87 .pay = 200'000'000'000'000'000,
88 .flags = kMptDexFlags});
89
90 MPTTester const btc(
91 {.env = env,
92 .issuer = gw_,
93 .holders = {alice_, bob_, carol_},
94 .pay = 2'000'000'000'000'000,
95 .flags = kMptDexFlags});
96
97 // Must be two offers at the same quality
98 // "taker gets" must be XRP
99 // (Different amounts so I can distinguish the offers)
100 env(offer(carol_, btc(49'000'000'000'000), XRP(49)));
101 env(offer(carol_, btc(51'000'000'000'000), XRP(51)));
102
103 // Offers for the poor quality path
104 // Must be two offers at the same quality
105 env(offer(carol_, XRP(50), eth(50'000'000'000'000)));
106 env(offer(carol_, XRP(50), eth(50'000'000'000'000)));
107
108 // Good quality path
109 AMM const ammCarol(env, carol_, btc(1'000'000'000'000'000), eth(100'100'000'000'000'000));
110
111 PathSet const paths(TestPath(XRP, MPT(eth)), TestPath(MPT(eth)));
112
113 env(pay(alice_, bob_, eth(100'000'000'000'000)),
114 Json(paths.json()),
115 Sendmax(btc(1'000'000'000'000'000)),
116 Txflags(tfPartialPayment));
117
118 BEAST_EXPECT(ammCarol.expectBalances(
119 btc(1'001'000'000'374'816), eth(100'000'000'000'000'000), ammCarol.tokens()));
120
121 env.require(Balance(bob_, eth(200'100'000'000'000'000)));
122 BEAST_EXPECT(isOffer(env, carol_, btc(49'000'000'000'000), XRP(49)));
123 }
124
125 void
127 {
128 testcase("Fill Modes");
129 using namespace jtx;
130
131 auto const startBalance = XRP(1'000'000);
132
133 // Fill or Kill - unless we fully cross, just charge a fee and don't
134 // place the offer on the books. But also clean up expired offers
135 // that are discovered along the way.
136 testAMM(
137 [&](AMM& ammAlice, Env& env) {
138 auto const& btc = MPT(ammAlice[1]);
139 auto const baseFee = env.current()->fees().base;
140 auto carolBTC = env.balance(carol_, btc);
141 auto carolXRP = env.balance(carol_, XRP);
142 // Order that can't be filled
143 env(offer(carol_, btc(100), XRP(100)), Txflags(tfFillOrKill), Ter(tecKILLED));
144 env.close();
145 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), btc(10'000), ammAlice.tokens()));
146 // fee = AMM
147 env.require(Balance(carol_, carolXRP - baseFee));
148 env.require(Balance(carol_, carolBTC));
149
150 BEAST_EXPECT(expectOffers(env, carol_, 0));
151 carolXRP = env.balance(carol_, XRP);
152
153 // Order that can be filled
154 env(offer(carol_, XRP(100), btc(100)), Txflags(tfFillOrKill), Ter(tesSUCCESS));
155 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(10'100), ammAlice.tokens()));
156 env.require(Balance(carol_, carolXRP + XRP(100) - baseFee));
157 env.require(Balance(carol_, carolBTC - btc(100)));
158 BEAST_EXPECT(expectOffers(env, carol_, 0));
159 },
160 {{XRP(10'100), gAmmmpt(10'000)}},
161 0,
162 std::nullopt,
163 {features});
164
165 // Immediate or Cancel - cross as much as possible
166 // and add nothing on the books.
167 testAMM(
168 [&](AMM& ammAlice, Env& env) {
169 auto const& btc = MPT(ammAlice[1]);
170 auto const baseFee = env.current()->fees().base;
171 auto carolBTC = env.balance(carol_, btc);
172 auto carolXRP = env.balance(carol_, XRP);
173 env(offer(carol_, XRP(200), btc(200)),
174 Txflags(tfImmediateOrCancel),
175 Ter(tesSUCCESS));
176
177 // AMM generates a synthetic offer of 100BTC/100XRP
178 // to match the CLOB offer quality.
179 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(10'100), ammAlice.tokens()));
180 // +AMM - offer * fee
181 env.require(Balance(carol_, carolXRP + XRP(100) - baseFee));
182 env.require(Balance(carol_, carolBTC - btc(100)));
183 BEAST_EXPECT(expectOffers(env, carol_, 0));
184 },
185 {{XRP(10'100), gAmmmpt(10'000)}},
186 0,
187 std::nullopt,
188 {features});
189
190 // tfPassive -- place the offer without crossing it.
191 testAMM(
192 [&](AMM& ammAlice, Env& env) {
193 // Carol creates a passive offer that could cross AMM.
194 // Carol's offer should stay in the ledger.
195 auto const& btc = MPT(ammAlice[1]);
196 env(offer(carol_, XRP(100), btc(100), tfPassive));
197 env.close();
198 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), btc(10'000), ammAlice.tokens()));
199 BEAST_EXPECT(expectOffers(env, carol_, 1, {{{XRP(100), btc(100)}}}));
200 },
201 {{XRP(10'100), gAmmmpt(10'000)}},
202 0,
203 std::nullopt,
204 {features});
205
206 // tfPassive -- cross only offers of better quality.
207 testAMM(
208 [&](AMM& ammAlice, Env& env) {
209 auto const& btc = MPT(ammAlice[1]);
210 env(offer(alice_, btc(110), XRP(100)));
211 env.close();
212
213 // Carol creates a passive offer. That offer should cross
214 // AMM and leave Alice's offer untouched.
215 env(offer(carol_, XRP(100), btc(100), tfPassive));
216 env.close();
217 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'900), btc(9083), ammAlice.tokens()));
218 BEAST_EXPECT(expectOffers(env, carol_, 0));
219 BEAST_EXPECT(expectOffers(env, alice_, 1));
220 },
221 {{XRP(11'000), gAmmmpt(9'000)}},
222 0,
223 std::nullopt,
224 {features});
225 }
226
227 void
229 {
230 testcase("Offer Crossing with XRP, Normal order");
231
232 using namespace jtx;
233
234 Env env{*this, features};
235
236 fund(env, gw_, {bob_, alice_}, XRP(300'000));
237
238 MPTTester const btc(
239 {.env = env,
240 .issuer = gw_,
241 .holders = {alice_, bob_},
242 .pay = 100'000'000,
243 .flags = kMptDexFlags});
244
245 AMM const ammAlice(env, alice_, XRP(150'000), btc(50'000'000));
246
247 // Existing offer pays better than this wants.
248 // Partially consume existing offer.
249 // Pay 1'000'000 BTC, get 3061224490 Drops.
250 auto const xrpTransferred = XRPAmount{3'061'224'490};
251 env(offer(bob_, btc(1'000'000), XRP(4'000)));
252
253 BEAST_EXPECT(ammAlice.expectBalances(
254 XRP(150'000) + xrpTransferred, btc(49'000'000), IOUAmount{273'861'278752583, -5}));
255
256 env.require(Balance(bob_, btc(101'000'000)));
257 BEAST_EXPECT(
258 expectLedgerEntryRoot(env, bob_, XRP(300'000) - xrpTransferred - 2 * txFee(env, 1)));
259 BEAST_EXPECT(expectOffers(env, bob_, 0));
260 }
261
262 void
264 {
265 testcase("Offer Crossing with Limit Override");
266
267 using namespace jtx;
268
269 Env env{*this, features};
270
271 env.fund(XRP(200'000), gw_, alice_, bob_);
272 env.close();
273
274 MPTTester const btc(
275 {.env = env, .issuer = gw_, .holders = {alice_, bob_}, .flags = kMptDexFlags});
276 env(pay(gw_, alice_, btc(500'000'000)));
277
278 AMM const ammAlice(env, alice_, XRP(150'000), btc(51'000'000));
279 env(offer(bob_, btc(1'000'000), XRP(3'000)));
280
281 BEAST_EXPECT(ammAlice.expectBalances(XRP(153'000), btc(50'000'000), ammAlice.tokens()));
282
283 env.require(Balance(bob_, btc(1'000'000)));
284 env.require(Balance(bob_, XRP(200'000) - XRP(3'000) - env.current()->fees().base * 2));
285 }
286
287 void
289 {
290 testcase("Currency Conversion: Entire Offer");
291
292 using namespace jtx;
293
294 Env env{*this, features};
295
296 fund(env, gw_, {alice_, bob_}, XRP(10'000));
297 env.require(Owners(bob_, 0));
298
299 MPTTester const btc(
300 {.env = env, .issuer = gw_, .holders = {alice_, bob_}, .flags = kMptDexFlags});
301 env(pay(gw_, bob_, btc(1'000'000'000)));
302
303 env.require(Owners(alice_, 1), Owners(bob_, 1));
304
305 env(pay(gw_, alice_, btc(100'000'000)));
306 AMM const ammBob(env, bob_, btc(200'000'000), XRP(1'500));
307
308 env(pay(alice_, alice_, XRP(500)), Sendmax(btc(100'000'000)));
309
310 BEAST_EXPECT(ammBob.expectBalances(btc(300'000'000), XRP(1'000), ammBob.tokens()));
311 env.require(Balance(alice_, btc(0)));
312
313 auto jrr = ledgerEntryRoot(env, alice_);
314 env.require(Balance(alice_, XRP(10'000) + XRP(500) - env.current()->fees().base * 2));
315 }
316
317 void
319 {
320 testcase("Currency Conversion: In Parts");
321
322 using namespace jtx;
323
324 Env env{*this, features};
325 env.fund(XRP(30'000), gw_, bob_);
326 env.fund(XRP(40'000), alice_);
327
328 MPTTester const btc(
329 {.env = env,
330 .issuer = gw_,
331 .holders = {alice_, bob_},
332 .pay = 30'000'000'000,
333 .flags = kMptDexFlags});
334 env(pay(gw_, alice_, btc(10'000'000'000)));
335
336 AMM const ammAlice(env, alice_, XRP(10'000), btc(10'000'000'000));
337 env.close();
338
339 // Alice converts BTC to XRP which should fail
340 // due to PartialPayment.
341 env(pay(alice_, alice_, XRP(100)), Sendmax(btc(100'000'000)), Ter(tecPATH_PARTIAL));
342
343 // Alice converts BTC to XRP, should succeed because
344 // we permit partial payment
345 env(pay(alice_, alice_, XRP(100)), Sendmax(btc(100'000'000)), Txflags(tfPartialPayment));
346 env.close();
347 BEAST_EXPECT(ammAlice.expectBalances(
348 XRPAmount{9'900'990'100}, btc(10'100'000'000), ammAlice.tokens()));
349 // initial 40,000'000'000 - 10,000'000'000AMM - 100'000'000pay
350 env.require(Balance(alice_, btc(29'900'000'000)));
351 // initial 40,000 - 10,0000AMM + 99.009900pay - fee*3
352 BEAST_EXPECT(expectLedgerEntryRoot(
353 env,
354 alice_,
355 XRP(40'000) - XRP(10'000) + XRPAmount{99'009'900} - ammCrtFee(env) - txFee(env, 3)));
356 }
357
358 void
360 {
361 testcase("Cross Currency Payment: Start with XRP");
362
363 using namespace jtx;
364
365 Env env{*this, features};
366 env.fund(XRP(30'000), gw_);
367 env.fund(XRP(40'000), alice_);
368 env.fund(XRP(1'000), bob_);
369
370 MPTTester const btc(
371 {.env = env, .issuer = gw_, .holders = {alice_, bob_}, .flags = kMptDexFlags});
372 env(pay(gw_, alice_, btc(10'100'000'000)));
373
374 AMM const ammAlice(env, alice_, XRP(10'000), btc(10'100'000'000));
375 env.close();
376
377 env(pay(alice_, bob_, btc(100'000'000)), Sendmax(XRP(100)));
378 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), btc(10'000'000'000), ammAlice.tokens()));
379 env.require(Balance(bob_, btc(100'000'000)));
380 }
381
382 void
384 {
385 testcase("Cross Currency Payment: End with XRP");
386
387 using namespace jtx;
388
389 Env env{*this, features};
390 env.fund(XRP(30'000), gw_);
391 env.fund(XRP(40'100), alice_);
392 env.fund(XRP(1'000), bob_);
393
394 MPTTester const btc(
395 {.env = env, .issuer = gw_, .holders = {alice_, bob_}, .flags = kMptDexFlags});
396 env(pay(gw_, alice_, btc(40'000'000'000)));
397
398 AMM const ammAlice(env, alice_, XRP(10'100), btc(10'000'000'000));
399 env.close();
400
401 env(pay(alice_, bob_, XRP(100)), Sendmax(btc(100'000'000)));
402 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(10'100'000'000), ammAlice.tokens()));
403 BEAST_EXPECT(expectLedgerEntryRoot(env, bob_, XRP(1'000) + XRP(100) - txFee(env, 1)));
404 }
405
406 void
408 {
409 testcase("Cross Currency Payment: Bridged");
410
411 using namespace jtx;
412
413 auto test = [&](auto&& issue1, auto&& issue2) {
414 Env env(*this);
415 auto const dan = Account{"dan"};
416 env.fund(XRP(60'000), alice_, bob_, carol_, gw_, dan);
417 env.close();
418 auto const eth = issue1(
419 {.env = env,
420 .token = "ETH",
421 .issuer = gw_,
422 .holders = {alice_, bob_, carol_, dan},
423 .limit = 10'000'000'000'000'000});
424 auto const btc = issue2(
425 {.env = env,
426 .token = "BTC",
427 .issuer = gw_,
428 .holders = {alice_, bob_, carol_, dan},
429 .limit = 10'000'000'000'000'000});
430 env(pay(gw_, alice_, btc(500'000'000'000'000)));
431 env(pay(gw_, carol_, btc(6'000'000'000'000'000)));
432 env(pay(gw_, dan, eth(400'000'000'000'000)));
433 env.close();
434 env.close();
435 AMM const ammCarol(env, carol_, btc(5'000'000'000'000'000), XRP(50'000));
436
437 env(offer(dan, XRP(500), eth(50'000'000'000'000)));
438 env.close();
439
441 jtp[0u][0u][jss::currency] = "XRP";
442 env(pay(alice_, bob_, eth(30'000'000'000'000)),
443 Json(jss::Paths, jtp),
444 Sendmax(btc(333'000'000'000'000)));
445 env.close();
446 BEAST_EXPECT(ammCarol.expectBalances(
447 XRP(49'700), btc(5'030'181'086'519'115), ammCarol.tokens()));
448 BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), eth(20'000'000'000'000)}}}));
449 env.require(Balance(bob_, eth(30'000'000'000'000)));
450 };
452 }
453
454 void
456 {
457 testcase("Offer Fees Consume Funds");
458
459 using namespace jtx;
460
461 Env env{*this, features};
462
463 // Provide micro amounts to compensate for fees to make results round
464 // nice.
465 auto const startingXrp =
466 XRP(100) + env.current()->fees().accountReserve(2) + env.current()->fees().base * 3;
467
468 env.fund(startingXrp, gw_, alice_);
469 env.fund(XRP(2'000), bob_);
470 env.close();
471
472 MPTTester const btc(
473 {.env = env, .issuer = gw_, .holders = {alice_, bob_}, .flags = kMptDexFlags});
474
475 // Created only to increase one reserve count for alice
476 MPTTester const eth(
477 {.env = env, .issuer = gw_, .holders = {alice_}, .flags = kMptDexFlags});
478
479 env(pay(gw_, bob_, btc(1'200'000'000'000'000)));
480
481 AMM const ammBob(env, bob_, XRP(1'000), btc(1'200'000'000'000'000));
482 // Alice has 400 - (2 reserve of 50 = 300 reserve) = 100 available.
483 // Ask for more than available to prove reserve works.
484 env(offer(alice_, btc(200'000'000'000'000), XRP(200)));
485
486 // The pool gets only 100XRP for ~109.09e12BTC, even though
487 // it can exchange more.
488 BEAST_EXPECT(
489 ammBob.expectBalances(XRP(1'100), btc(1'090'909'090'909'091), ammBob.tokens()));
490
491 env.require(Balance(alice_, btc(109'090'909'090'909)));
492 env.require(Balance(alice_, XRP(300)));
493 }
494
495 void
497 {
498 testcase("Offer Create, then Cross");
499
500 using namespace jtx;
501
502 Env env{*this, features};
503
504 fund(env, gw_, {alice_, bob_}, XRP(200'000));
505
506 MPTTester const btc(
507 {.env = env,
508 .issuer = gw_,
509 .holders = {alice_, bob_},
510 .transferFee = 500,
511 .flags = kMptDexFlags});
512
513 env(pay(gw_, bob_, btc(1'000'000'000'000)));
514 env(pay(gw_, alice_, btc(200'000'000'000'000)));
515
516 AMM const ammAlice(env, alice_, btc(150'000'000'000'000), XRP(150'100));
517 env(offer(bob_, XRP(100), btc(100'000'000'000)));
518
519 BEAST_EXPECT(
520 ammAlice.expectBalances(btc(150'100'000'000'000), XRP(150'000), ammAlice.tokens()));
521
522 // Bob pays 0.005 transfer fee.
523 env.require(Balance(bob_, btc(899'500'000'000)));
524 }
525
526 void
528 {
529 testcase("Offer tfSell: Basic Sell");
530
531 using namespace jtx;
532
533 Env env{*this, features};
534 env.fund(XRP(30'000), gw_, bob_, carol_);
535 env.fund(XRP(39'900), alice_);
536
537 MPTTester const btc(
538 {.env = env,
539 .issuer = gw_,
540 .holders = {alice_, bob_, carol_},
541 .pay = 30'000,
542 .flags = kMptDexFlags});
543 env(pay(gw_, alice_, btc(10'100)));
544
545 AMM const ammAlice(env, alice_, XRP(9'900), btc(10'100));
546
547 env(offer(carol_, btc(100), XRP(100)), Json(jss::Flags, tfSell));
548 env.close();
549 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(9'999), ammAlice.tokens()));
550 BEAST_EXPECT(expectOffers(env, carol_, 0));
551 env.require(Balance(carol_, btc(30'101)));
552 BEAST_EXPECT(
553 expectLedgerEntryRoot(env, carol_, XRP(30'000) - XRP(100) - 2 * txFee(env, 1)));
554 }
555
556 void
558 {
559 testcase("Offer tfSell: 2x Sell Exceed Limit");
560
561 using namespace jtx;
562
563 Env env{*this, features};
564
565 auto const startingXrp = XRP(100) + reserve(env, 1) + env.current()->fees().base * 2;
566
567 env.fund(startingXrp, gw_, alice_);
568 env.fund(XRP(2'000), bob_);
569 env.close();
570
571 MPTTester const btc(
572 {.env = env, .issuer = gw_, .holders = {alice_, bob_}, .flags = kMptDexFlags});
573 env(pay(gw_, bob_, btc(2'200'000'000)));
574
575 AMM const ammBob(env, bob_, XRP(1'000), btc(2'200'000'000));
576 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
577 // Ask for more than available to prove reserve works.
578 // Taker pays 100'000'000 BTC for 100 XRP.
579 // Selling XRP.
580 // Will sell all 100 XRP and get more BTC than asked for.
581 env(offer(alice_, btc(100'000'000), XRP(200)), Json(jss::Flags, tfSell));
582 BEAST_EXPECT(ammBob.expectBalances(XRP(1'100), btc(2'000'000'000), ammBob.tokens()));
583 env.require(Balance(alice_, btc(200'000'000)));
584 BEAST_EXPECT(expectLedgerEntryRoot(env, alice_, XRP(250)));
585 BEAST_EXPECT(expectOffers(env, alice_, 0));
586 }
587
588 void
590 {
591 testcase("Client Issue: Gateway Cross Currency");
592
593 using namespace jtx;
594
595 Env env{*this, features};
596
597 auto const startingXrp = XRP(100.1) + reserve(env, 1) + env.current()->fees().base * 2;
598 env.fund(startingXrp, gw_, alice_, bob_);
599
600 MPTTester const xts(
601 {.env = env,
602 .issuer = gw_,
603 .holders = {alice_, bob_},
604 .pay = 1'000'000'000'000'000,
605 .flags = kMptDexFlags});
606 MPTTester const xxx(
607 {.env = env,
608 .issuer = gw_,
609 .holders = {alice_, bob_},
610 .pay = 1'000'000'000'000'000,
611 .flags = kMptDexFlags});
612
613 AMM const ammAlice(env, alice_, xts(1'000'000'000'000'000), xxx(1'000'000'000'000'000));
614
615 json::Value payment;
616 payment[jss::secret] = toBase58(generateSeed("bob"));
617 payment[jss::id] = env.seq(bob_);
618 payment[jss::build_path] = true;
619 payment[jss::tx_json] = pay(bob_, bob_, xxx(10'000'000'000'000));
620 payment[jss::tx_json][jss::Sequence] =
621 env.current()->read(keylet::account(bob_.id()))->getFieldU32(sfSequence);
622 payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
623 payment[jss::tx_json][jss::SendMax] =
624 xts(15'000'000'000'000).value().getJson(JsonOptions::Values::None);
625 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
626 auto const jrr = env.rpc("json", "submit", to_string(payment));
627 BEAST_EXPECT(jrr[jss::result][jss::status] == "success");
628 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
629
630 BEAST_EXPECT(ammAlice.expectBalances(
631 xts(1'010'101'010'101'011), xxx(990'000'000'000'000), ammAlice.tokens()));
632 env.require(Balance(bob_, xts(989'898'989'898'989)));
633 env.require(Balance(bob_, xxx(1'010'000'000'000'000)));
634 }
635
636 void
638 {
639 testcase("Bridged Crossing");
640
641 using namespace jtx;
642
643 {
644 Env env{*this, features};
645 env.fund(XRP(30'000), gw_, alice_, bob_, carol_);
646
647 MPTTester const btc(
648 {.env = env,
649 .issuer = gw_,
650 .holders = {alice_, bob_, carol_},
651 .pay = 15'000'000'000,
652 .flags = kMptDexFlags});
653 MPTTester const eth(
654 {.env = env,
655 .issuer = gw_,
656 .holders = {alice_, bob_, carol_},
657 .pay = 15'000'000'000,
658 .flags = kMptDexFlags});
659
660 // The scenario:
661 // o BTC/XRP AMM is created.
662 // o ETH/XRP AMM is created.
663 // o carol has ETH but wants BTC.
664 // Note that carol's offer must come last. If carol's offer is
665 // placed before AMM is created, then autobridging will not occur.
666 AMM const ammAlice(env, alice_, XRP(10'000), btc(10'100'000'000));
667 AMM const ammBob(env, bob_, eth(10'000'000'000), XRP(10'100));
668
669 // Carol makes an offer that consumes AMM liquidity and
670 // fully consumes Carol's offer.
671 env(offer(carol_, btc(100'000'000), eth(100'000'000)));
672 env.close();
673
674 BEAST_EXPECT(
675 ammAlice.expectBalances(XRP(10'100), btc(10'000'000'000), ammAlice.tokens()));
676 BEAST_EXPECT(ammBob.expectBalances(XRP(10'000), eth(10'100'000'000), ammBob.tokens()));
677 env.require(Balance(carol_, btc(15'100'000'000)));
678 env.require(Balance(carol_, eth(14'900'000'000)));
679 BEAST_EXPECT(expectOffers(env, carol_, 0));
680 }
681
682 {
683 Env env{*this, features};
684 env.fund(XRP(30'000), gw_, alice_, bob_, carol_);
685
686 MPTTester const btc(
687 {.env = env,
688 .issuer = gw_,
689 .holders = {alice_, bob_, carol_},
690 .pay = 15'000'000'000,
691 .flags = kMptDexFlags});
692 MPTTester const eth(
693 {.env = env,
694 .issuer = gw_,
695 .holders = {alice_, bob_, carol_},
696 .pay = 15'000'000'000,
697 .flags = kMptDexFlags});
698
699 // The scenario:
700 // o BTC/XRP AMM is created.
701 // o ETH/XRP offer is created.
702 // o carol has ETH but wants BTC.
703 // Note that carol's offer must come last. If carol's offer is
704 // placed before AMM and bob's offer are created, then autobridging
705 // will not occur.
706 AMM const ammAlice(env, alice_, XRP(10'000), btc(10'100'000'000));
707 env(offer(bob_, eth(100'000'000), XRP(100)));
708 env.close();
709
710 // Carol makes an offer that consumes AMM liquidity and
711 // fully consumes Carol's offer.
712 env(offer(carol_, btc(100'000'000), eth(100'000'000)));
713 env.close();
714
715 BEAST_EXPECT(
716 ammAlice.expectBalances(XRP(10'100), btc(10'000'000'000), ammAlice.tokens()));
717 env.require(Balance(carol_, btc(15'100'000'000)));
718 env.require(Balance(carol_, eth(14'900'000'000)));
719 BEAST_EXPECT(expectOffers(env, carol_, 0));
720 BEAST_EXPECT(expectOffers(env, bob_, 0));
721 }
722
723 {
724 Env env{*this, features};
725 env.fund(XRP(30'000), gw_, alice_, bob_, carol_);
726
727 MPTTester const btc(
728 {.env = env,
729 .issuer = gw_,
730 .holders = {alice_, bob_, carol_},
731 .pay = 15'000'000'000,
732 .flags = kMptDexFlags});
733 MPTTester const eth(
734 {.env = env,
735 .issuer = gw_,
736 .holders = {alice_, bob_, carol_},
737 .pay = 15'000'000'000,
738 .flags = kMptDexFlags});
739
740 // The scenario:
741 // o BTC/XRP offer is created.
742 // o ETH/XRP AMM is created.
743 // o carol has ETH but wants BTC.
744 // Note that carol's offer must come last. If carol's offer is
745 // placed before AMM and alice's offer are created, then
746 // autobridging will not occur.
747 env(offer(alice_, XRP(100), btc(100'000'000)));
748 env.close();
749 AMM const ammBob(env, bob_, eth(10'000'000'000), XRP(10'100));
750
751 // Carol makes an offer that consumes AMM liquidity and
752 // fully consumes Carol's offer.
753 env(offer(carol_, btc(100'000'000), eth(100'000'000)));
754 env.close();
755
756 BEAST_EXPECT(ammBob.expectBalances(XRP(10'000), eth(10'100'000'000), ammBob.tokens()));
757 env.require(Balance(carol_, btc(15'100'000'000)));
758 env.require(Balance(carol_, eth(14'900'000'000)));
759 BEAST_EXPECT(expectOffers(env, carol_, 0));
760 BEAST_EXPECT(expectOffers(env, alice_, 0));
761 }
762 }
763
764 void
766 {
767 // Test a number of different corner cases regarding offer crossing
768 // when both the tfSell flag and tfFillOrKill flags are set.
769 testcase("Combine tfSell with tfFillOrKill");
770
771 using namespace jtx;
772
773 {
774 Env env{*this, features};
775 env.fund(XRP(30'000), gw_, alice_, bob_);
776
777 MPTTester const btc(
778 {.env = env,
779 .issuer = gw_,
780 .holders = {alice_, bob_},
781 .pay = 20'000'000'000,
782 .flags = kMptDexFlags});
783 AMM const ammBob(env, bob_, XRP(20'000), btc(200'000'000));
784 // alice submits a tfSell | tfFillOrKill offer that does not cross.
785 env(offer(alice_, btc(2'100'000), XRP(210), tfSell | tfFillOrKill), Ter(tecKILLED));
786
787 BEAST_EXPECT(ammBob.expectBalances(XRP(20'000), btc(200'000'000), ammBob.tokens()));
788 BEAST_EXPECT(expectOffers(env, bob_, 0));
789 }
790 {
791 Env env{*this, features};
792 env.fund(XRP(30'000), gw_, alice_, bob_);
793
794 MPTTester const btc(
795 {.env = env,
796 .issuer = gw_,
797 .holders = {alice_, bob_},
798 .pay = 1'000'000'000'000'000,
799 .flags = kMptDexFlags});
800 AMM const ammBob(env, bob_, XRP(20'000), btc(200'000'000'000'000));
801 // alice submits a tfSell | tfFillOrKill offer that crosses.
802 // Even though tfSell is present it doesn't matter this time.
803 env(offer(alice_, btc(2'000'000'000'000), XRP(220), tfSell | tfFillOrKill));
804 env.close();
805 BEAST_EXPECT(
806 ammBob.expectBalances(XRP(20'220), btc(197'823'936'696'341), ammBob.tokens()));
807 env.require(Balance(alice_, btc(1'002'176'063'303'659)));
808 BEAST_EXPECT(expectOffers(env, alice_, 0));
809 }
810 {
811 // alice submits a tfSell | tfFillOrKill offer that crosses and
812 // returns more than was asked for (because of the tfSell flag).
813 Env env{*this, features};
814 env.fund(XRP(30'000), gw_, alice_, bob_);
815
816 MPTTester const btc(
817 {.env = env,
818 .issuer = gw_,
819 .holders = {alice_, bob_},
820 .pay = 1'000'000'000'000'000,
821 .flags = kMptDexFlags});
822 AMM const ammBob(env, bob_, XRP(20'000), btc(200'000'000'000'000));
823
824 env(offer(alice_, btc(10'000'000'000'000), XRP(1'500), tfSell | tfFillOrKill));
825 env.close();
826
827 BEAST_EXPECT(
828 ammBob.expectBalances(XRP(21'500), btc(186'046'511'627'907), ammBob.tokens()));
829 env.require(Balance(alice_, btc(1'013'953'488'372'093)));
830 BEAST_EXPECT(expectOffers(env, alice_, 0));
831 }
832 {
833 // alice submits a tfSell | tfFillOrKill offer that doesn't cross.
834 // This would have succeeded with a regular tfSell, but the
835 // fillOrKill prevents the transaction from crossing since not
836 // all of the offer is consumed because AMM generated offer,
837 // which matches alice's offer quality is ~ 10XRP/0.01996e3BTC.
838 Env env{*this, features};
839 env.fund(XRP(30'000), gw_, alice_, bob_);
840
841 MPTTester const btc(
842 {.env = env,
843 .issuer = gw_,
844 .holders = {alice_, bob_},
845 .pay = 10'000'000'000,
846 .flags = kMptDexFlags});
847 AMM const ammBob(env, bob_, XRP(5000), btc(10'000'000));
848
849 env(offer(alice_, btc(1'000'000), XRP(501), tfSell | tfFillOrKill), Ter(tecKILLED));
850 env.close();
851 BEAST_EXPECT(expectOffers(env, alice_, 0));
852 BEAST_EXPECT(expectOffers(env, bob_, 0));
853 }
854 }
855
856 void
858 {
859 testcase("Transfer Rate Offer");
860
861 using namespace jtx;
862
863 // AMM XRP/BTC. Alice places BTC/XRP offer.
864 {
865 Env env(*this, features);
866 env.fund(XRP(30'000), gw_, bob_, carol_);
867 env.fund(XRP(40'000), alice_);
868
869 MPTTester const btc(
870 {.env = env,
871 .issuer = gw_,
872 .holders = {alice_, bob_, carol_},
873 .transferFee = 25'000,
874 .pay = 30'000'000,
875 .flags = kMptDexFlags});
876 env(pay(gw_, alice_, btc(10'100'000)));
877
878 AMM const ammAlice(env, alice_, XRP(10'000), btc(10'100'000));
879 env.close();
880
881 env(offer(carol_, btc(100'000), XRP(100)));
882 env.close();
883
884 // AMM doesn't pay the transfer fee
885 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), btc(10'000'000), ammAlice.tokens()));
886 env.require(Balance(carol_, btc(30'100'000)));
887 BEAST_EXPECT(expectOffers(env, carol_, 0));
888 }
889
890 {
891 Env env(*this, features);
892 env.fund(XRP(30'000), gw_, bob_, carol_);
893 env.fund(XRP(40'100), alice_);
894
895 MPTTester const btc(
896 {.env = env,
897 .issuer = gw_,
898 .holders = {alice_, bob_, carol_},
899 .transferFee = 25'000,
900 .pay = 30'000'000,
901 .flags = kMptDexFlags});
902 env(pay(gw_, alice_, btc(10'000'000)));
903
904 AMM const ammAlice(env, alice_, XRP(10'100), btc(10'000'000));
905 env.close();
906
907 env(offer(carol_, XRP(100), btc(100'000)));
908 env.close();
909
910 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(10'100'000), ammAlice.tokens()));
911 // Carol pays 25% transfer fee
912 env.require(Balance(carol_, btc(29'875'000)));
913 BEAST_EXPECT(expectOffers(env, carol_, 0));
914 }
915
916 {
917 // Bridged crossing.
918 Env env{*this, features};
919 env.fund(XRP(30'000), gw_, alice_, bob_, carol_);
920
921 MPTTester const btc(
922 {.env = env,
923 .issuer = gw_,
924 .holders = {alice_, bob_, carol_},
925 .transferFee = 25'000,
926 .pay = 15'000'000,
927 .flags = kMptDexFlags});
928 MPTTester const eth(
929 {.env = env,
930 .issuer = gw_,
931 .holders = {alice_, bob_, carol_},
932 .transferFee = 25'000,
933 .pay = 15'000'000,
934 .flags = kMptDexFlags});
935
936 // The scenario:
937 // o BTC/XRP AMM is created.
938 // o ETH/XRP Offer is created.
939 // o carol has ETH but wants BTC.
940 // Note that Carol's offer must come last. If Carol's offer is
941 // placed before AMM is created, then autobridging will not occur.
942 AMM const ammAlice(env, alice_, XRP(10'000), btc(10'100'000));
943 env(offer(bob_, eth(100'000), XRP(100)));
944 env.close();
945
946 // Carol makes an offer that consumes AMM liquidity and
947 // fully consumes Bob's offer.
948 env(offer(carol_, btc(100'000), eth(100'000)));
949 env.close();
950
951 // AMM doesn't pay the transfer fee
952 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), btc(10'000'000), ammAlice.tokens()));
953 env.require(Balance(carol_, btc(15'100'000)));
954 // Carol pays 25% transfer fee.
955 env.require(Balance(carol_, eth(14'875'000)));
956 BEAST_EXPECT(expectOffers(env, carol_, 0));
957 BEAST_EXPECT(expectOffers(env, bob_, 0));
958 }
959
960 {
961 // Bridged crossing. The transfer fee is paid on the step not
962 // involving AMM as src/dst.
963 Env env{*this, features};
964 env.fund(XRP(30'000), gw_, alice_, bob_, carol_);
965
966 MPTTester const btc(
967 {.env = env,
968 .issuer = gw_,
969 .holders = {alice_, bob_, carol_},
970 .transferFee = 25'000,
971 .pay = 15'000'000,
972 .flags = kMptDexFlags});
973 MPTTester const eth(
974 {.env = env,
975 .issuer = gw_,
976 .holders = {alice_, bob_, carol_},
977 .transferFee = 25'000,
978 .pay = 15'000'000,
979 .flags = kMptDexFlags});
980
981 // The scenario:
982 // o BTC/XRP AMM is created.
983 // o ETH/XRP Offer is created.
984 // o carol has ETH but wants BTC.
985 // Note that Carol's offer must come last. If Carol's offer is
986 // placed before AMM is created, then autobridging will not occur.
987 AMM const ammAlice(env, alice_, XRP(10'000), btc(10'050'000));
988 env(offer(bob_, eth(100'000), XRP(100)));
989 env.close();
990
991 // Carol makes an offer that consumes AMM liquidity and
992 // partially consumes Bob's offer.
993 env(offer(carol_, btc(50'000), eth(50'000)));
994 env.close();
995 // This test verifies that the amount removed from an offer
996 // accounts for the transfer fee that is removed from the
997 // account but not from the remaining offer.
998
999 // AMM doesn't pay the transfer fee
1000 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'050), btc(10'000'000), ammAlice.tokens()));
1001 env.require(Balance(carol_, btc(15'050'000)));
1002 // Carol pays 25% transfer fee.
1003 env.require(Balance(carol_, eth(14'937'500)));
1004 BEAST_EXPECT(expectOffers(env, carol_, 0));
1005 BEAST_EXPECT(expectOffers(env, bob_, 1, {{Amounts{eth(50'000), XRP(50)}}}));
1006 }
1007 }
1008
1009 void
1011 {
1012 // This test is not the same as corresponding testSelfIssueOffer()
1013 // in the Offer_test. It simply tests AMM with self issue and
1014 // offer crossing.
1015 using namespace jtx;
1016
1017 Env env{*this, features};
1018
1019 auto const f = env.current()->fees().base;
1020
1021 env.fund(XRP(30'000) + f, alice_, bob_);
1022 env.close();
1023
1024 MPTTester const btc(
1025 {.env = env, .issuer = bob_, .holders = {alice_}, .flags = kMptDexFlags});
1026
1027 AMM const ammBob(env, bob_, XRP(10'000), btc(10'100));
1028
1029 env(offer(alice_, btc(100), XRP(100)));
1030 env.close();
1031
1032 BEAST_EXPECT(ammBob.expectBalances(XRP(10'100), btc(10'000), ammBob.tokens()));
1033 BEAST_EXPECT(expectOffers(env, alice_, 0));
1034 env.require(Balance(alice_, btc(100)));
1035 }
1036
1037 void
1039 {
1040 // The offer crossing code expects that a DirectStep is always
1041 // preceded by a BookStep. In one instance the default path
1042 // was not matching that assumption. Here we recreate that case
1043 // so we can prove the bug stays fixed.
1044 testcase("Direct to Direct path");
1045
1046 using namespace jtx;
1047
1048 Env env{*this, features};
1049
1050 auto const ann = Account("ann");
1051 auto const bob = Account("bob");
1052 auto const cam = Account("cam");
1053 auto const carol = Account("carol");
1054
1055 auto const fee = env.current()->fees().base;
1056 env.fund(XRP(1'000), carol);
1057 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
1058 env.close();
1059
1060 MPTTester const aBux(
1061 {.env = env, .issuer = ann, .holders = {bob, cam, carol}, .flags = kMptDexFlags});
1062
1063 MPTTester const bBux(
1064 {.env = env, .issuer = bob, .holders = {ann, cam, carol}, .flags = kMptDexFlags});
1065
1066 env(pay(ann, cam, aBux(350'000'000'000'000)));
1067 env(pay(bob, cam, bBux(350'000'000'000'000)));
1068 env(pay(bob, carol, bBux(4'000'000'000'000'000)));
1069 env(pay(ann, carol, aBux(4'000'000'000'000'000)));
1070
1071 AMM const ammCarol(env, carol, aBux(3'000'000'000'000'000), bBux(3'300'000'000'000'000));
1072
1073 // cam puts an offer on the books that her upcoming offer could cross.
1074 // But this offer should be deleted, not crossed, by her upcoming
1075 // offer.
1076 env(offer(cam, aBux(290'000'000'000'000), bBux(300'000'000'000'000), tfPassive));
1077 env.close();
1078 env.require(Balance(cam, aBux(350'000'000'000'000)));
1079 env.require(Balance(cam, bBux(350'000'000'000'000)));
1080 env.require(offers(cam, 1));
1081
1082 // This offer caused the assert.
1083 env(offer(cam, bBux(300'000'000'000'000), aBux(300'000'000'000'000)));
1084
1085 // AMM is consumed up to the first cam Offer quality
1086 BEAST_EXPECT(ammCarol.expectBalances(
1087 aBux(3'093'541'659'651'604), bBux(3'200'215'509'984'418), ammCarol.tokens()));
1088 BEAST_EXPECT(expectOffers(
1089 env, cam, 1, {{Amounts{bBux(200'215'509'984'418), aBux(200'215'509'984'419)}}}));
1090 }
1091
1092 void
1094 {
1095 testcase("RequireAuth");
1096
1097 using namespace jtx;
1098
1099 Env env{*this, features};
1100 env.fund(XRP(400'000), gw_, alice_, bob_);
1101
1102 MPTTester btc(
1103 {.env = env,
1104 .issuer = gw_,
1105 .holders = {alice_, bob_},
1106 .flags = tfMPTRequireAuth | kMptDexFlags});
1107
1108 // Authorize bob and alice
1109 btc.authorize({.holder = alice_});
1110 btc.authorize({.holder = bob_});
1111
1112 env(pay(gw_, alice_, btc(1'000)));
1113 env.close();
1114
1115 // Alice is able to create AMM since the GW has authorized her
1116 AMM const ammAlice(env, alice_, btc(1'000), XRP(1'050));
1117
1118 env(pay(gw_, bob_, btc(50)));
1119 env.close();
1120
1121 env.require(Balance(bob_, btc(50)));
1122
1123 // Bob's offer should cross Alice's AMM
1124 env(offer(bob_, XRP(50), btc(50)));
1125 env.close();
1126
1127 BEAST_EXPECT(ammAlice.expectBalances(btc(1'050), XRP(1'000), ammAlice.tokens()));
1128 BEAST_EXPECT(expectOffers(env, bob_, 0));
1129 env.require(Balance(bob_, btc(0)));
1130 }
1131
1132 void
1134 {
1135 testcase("Missing Auth");
1136
1137 using namespace jtx;
1138
1139 Env env{*this, features};
1140
1141 env.fund(XRP(400'000), gw_, alice_, bob_);
1142 env.close();
1143
1144 MPTTester btc(
1145 {.env = env,
1146 .issuer = gw_,
1147 .holders = {alice_, bob_},
1148 .flags = tfMPTRequireAuth | kMptDexFlags});
1149
1150 // Alice doesn't have the funds
1151 {
1152 AMM const ammAlice(env, alice_, btc(1'000), XRP(1'000), Ter(tecNO_AUTH));
1153 }
1154
1155 btc.authorize({.holder = bob_});
1156 env(pay(gw_, bob_, btc(50)));
1157 env.close();
1158 env.require(Balance(bob_, btc(50)));
1159
1160 // Alice should not be able to create AMM without authorization.
1161 {
1162 AMM const ammAlice(env, alice_, btc(1'000), XRP(1'000), Ter(tecNO_AUTH));
1163 }
1164
1165 // Finally, authorize alice. Now alice's AMM create should succeed.
1166 btc.authorize({.holder = alice_});
1167 env(pay(gw_, alice_, btc(1'000)));
1168 env.close();
1169
1170 AMM const ammAlice(env, alice_, btc(1'000), XRP(1'050));
1171
1172 // Authorize AMM.
1173 // BTC.authorize({.account = ammAlice.ammAccount()});
1174 // env.close();
1175
1176 // Now bob creates his offer again, which crosses with alice's AMM.
1177 env(offer(bob_, XRP(50), btc(50)));
1178 env.close();
1179
1180 BEAST_EXPECT(ammAlice.expectBalances(btc(1'050), XRP(1'000), ammAlice.tokens()));
1181 BEAST_EXPECT(expectOffers(env, bob_, 0));
1182 env.require(Balance(bob_, btc(0)));
1183 }
1184
1185 void
1212
1213 void
1215 {
1216 testcase("path find consume all");
1217 using namespace jtx;
1218
1219 Env env = pathTestEnv();
1220 env.fund(XRP(100'000'260), alice_);
1221 env.fund(XRP(30'000), gw_, bob_, carol_);
1222
1223 MPTTester const eth(
1224 {.env = env,
1225 .issuer = gw_,
1226 .holders = {alice_, bob_, carol_},
1227 .pay = 100'000'000'000'000,
1228 .flags = kMptDexFlags});
1229
1230 AMM const ammCarol(env, carol_, XRP(100), eth(100'000'000'000'000));
1231
1232 STPathSet st;
1233 STAmount sa;
1234 STAmount da;
1235 std::tie(st, sa, da) = findPaths(
1236 env, alice_, bob_, bob_["AUD"](-1), std::optional<STAmount>(XRP(100'000'000)));
1237 BEAST_EXPECT(st.empty());
1238 std::tie(st, sa, da) =
1239 findPaths(env, alice_, bob_, eth(-1), std::optional<STAmount>(XRP(100'000'000)));
1240 // Alice sends all requested 100,000,000XRP
1241 BEAST_EXPECT(sa == XRP(100'000'000));
1242 // Bob gets ~99.99e12ETH. This is the amount Bob
1243 // can get out of AMM for 100,000,000XRP.
1244 BEAST_EXPECT(equal(da, eth(99'999'900'000'100)));
1245 }
1246
1247 // carol holds ETH, sells ETH for XRP
1248 // bob will hold ETH
1249 // alice pays bob ETH using XRP
1250 void
1252 {
1253 testcase("via gateway");
1254 using namespace jtx;
1255
1256 Env env = pathTestEnv();
1257 env.fund(XRP(10'000), alice_, bob_, carol_, gw_);
1258 env.close();
1259
1260 MPTTester const eth(
1261 {.env = env,
1262 .issuer = gw_,
1263 .holders = {alice_, bob_, carol_},
1264 .transferFee = 10'000,
1265 .flags = kMptDexFlags});
1266
1267 MPTTester const btc(
1268 {.env = env,
1269 .issuer = gw_,
1270 .holders = {alice_, bob_, carol_},
1271 .transferFee = 10'000,
1272 .flags = kMptDexFlags});
1273
1274 env(pay(gw_, carol_, eth(51)));
1275 env.close();
1276 AMM const ammCarol(env, carol_, XRP(40), eth(51));
1277 env(pay(alice_, bob_, eth(10)), Sendmax(XRP(100)), Paths(XRP));
1278 env.close();
1279 // AMM offer is 51.282052XRP/11ETH, 11ETH/1.1 = 10ETH to bob
1280 BEAST_EXPECT(ammCarol.expectBalances(XRP(51), eth(40), ammCarol.tokens()));
1281 env.require(Balance(bob_, eth(10)));
1282
1283 auto const result = findPaths(env, alice_, bob_, btc(25));
1284 BEAST_EXPECT(std::get<0>(result).empty());
1285 }
1286
1287 void
1289 {
1290 testcase("Receive max");
1291 using namespace jtx;
1292 auto const charlie = Account("charlie");
1293 {
1294 // XRP -> MPT receive max
1295 Env env = pathTestEnv();
1296 env.fund(XRP(30'000), alice_, bob_, charlie, gw_);
1297
1298 MPTTester const eth(
1299 {.env = env,
1300 .issuer = gw_,
1301 .holders = {alice_, bob_, charlie},
1302 .pay = 11'000'000'000'000,
1303 .flags = kMptDexFlags});
1304
1305 AMM const ammCharlie(env, charlie, XRP(10), eth(11'000'000'000'000));
1306 auto [st, sa, da] = findPaths(env, alice_, bob_, eth(-1), XRP(1).value());
1307 BEAST_EXPECT(sa == XRP(1));
1308 BEAST_EXPECT(equal(da, eth(1'000'000'000'000)));
1309 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1310 {
1311 auto const& pathElem = st[0][0];
1312 BEAST_EXPECT(
1313 pathElem.isOffer() && pathElem.getIssuerID() == gw_.id() &&
1314 pathElem.getMPTID() == eth.issuanceID());
1315 }
1316 }
1317 {
1318 // MPT -> XRP receive max
1319 Env env = pathTestEnv();
1320 env.fund(XRP(30'000), alice_, bob_, charlie, gw_);
1321
1322 MPTTester const eth(
1323 {.env = env,
1324 .issuer = gw_,
1325 .holders = {alice_, bob_, charlie},
1326 .pay = 11'000'000'000'000,
1327 .flags = kMptDexFlags});
1328
1329 AMM const ammCharlie(env, charlie, XRP(11), eth(10'000'000'000'000));
1330 env.close();
1331 auto [st, sa, da] =
1332 findPaths(env, alice_, bob_, drops(-1), eth(1'000'000'000'000).value());
1333 BEAST_EXPECT(sa == eth(1'000'000'000'000));
1334 BEAST_EXPECT(equal(da, XRP(1)));
1335 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1336 {
1337 auto const& pathElem = st[0][0];
1338 BEAST_EXPECT(
1339 pathElem.isOffer() && pathElem.getIssuerID() == xrpAccount() &&
1340 pathElem.getCurrency() == xrpCurrency());
1341 }
1342 }
1343 }
1344
1345 void
1347 {
1348 testcase("Path Find: XRP -> XRP and XRP -> MPT");
1349 using namespace jtx;
1350 Env env = pathTestEnv();
1351 Account a1{"A1"};
1352 Account a2{"A2"};
1353 Account a3{"A3"};
1354 Account const g1{"G1"};
1355 Account const g2{"G2"};
1356 Account g3{"G3"};
1357 Account m1{"M1"};
1358
1359 env.fund(XRP(100'000), a1);
1360 env.fund(XRP(10'000), a2);
1361 env.fund(XRP(1'000), a3, g1, g2, g3);
1362 env.fund(XRP(20'000), m1);
1363 env.close();
1364
1365 MPTTester const xyzG1(
1366 {.env = env, .issuer = g1, .holders = {a1, m1, a2}, .flags = kMptDexFlags});
1367
1368 MPTTester const xyzG2(
1369 {.env = env, .issuer = g2, .holders = {a2, m1, a1}, .flags = kMptDexFlags});
1370
1371 MPTTester const abcG3(
1372 {.env = env, .issuer = g3, .holders = {a1, a2, m1, a3}, .flags = kMptDexFlags});
1373
1374 MPTTester const abcA2(
1375 {.env = env, .issuer = a2, .holders = {g3, a1}, .flags = kMptDexFlags});
1376
1377 env(pay(g1, a1, xyzG1(3'500'000'000)));
1378 env(pay(g3, a1, abcG3(1'200'000'000)));
1379 env(pay(g1, m1, xyzG1(25'000'000'000)));
1380 env(pay(g2, m1, xyzG2(25'000'000'000)));
1381 env(pay(g3, m1, abcG3(25'000'000'000)));
1382 env(pay(a2, g3, abcA2(101'000'000)));
1383 env.close();
1384
1385 AMM const ammM1XyzG1XyzG2(env, m1, xyzG1(1'000'000'000), xyzG2(1'000'000'000));
1386 AMM const ammM1XrpAbcG3(env, m1, XRP(10'000), abcG3(1'000'000'000));
1387 AMM const ammG3AbcG3AbcA2(env, g3, abcG3(100'000'000), abcA2(101'000'000));
1388 env.close();
1389
1390 STPathSet st;
1391 STAmount sa, da;
1392
1393 {
1394 auto const& sendAmt = XRP(10);
1395 std::tie(st, sa, da) = findPaths(env, a1, a2, sendAmt, std::nullopt, xrpCurrency());
1396 BEAST_EXPECT(equal(da, sendAmt));
1397 BEAST_EXPECT(st.empty());
1398 }
1399
1400 {
1401 // no path should exist for this since dest account
1402 // does not exist.
1403 auto const& sendAmt = XRP(200);
1404 std::tie(st, sa, da) =
1405 findPaths(env, a1, Account{"A0"}, sendAmt, std::nullopt, xrpCurrency());
1406 BEAST_EXPECT(equal(da, sendAmt));
1407 BEAST_EXPECT(st.empty());
1408 }
1409
1410 {
1411 auto const& sendAmt = abcG3(10'000'000);
1412 std::tie(st, sa, da) = findPaths(env, a2, g3, sendAmt, std::nullopt, xrpCurrency());
1413 BEAST_EXPECT(equal(da, sendAmt));
1414 BEAST_EXPECT(equal(sa, XRPAmount{101'010'102}));
1415 BEAST_EXPECT(same(st, stpath(ipe(MPT(abcG3)))));
1416 }
1417
1418 {
1419 auto const& sendAmt = abcA2(1'000'000);
1420 std::tie(st, sa, da) = findPaths(env, a1, a2, sendAmt, std::nullopt, xrpCurrency());
1421 BEAST_EXPECT(equal(da, sendAmt));
1422 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
1423 BEAST_EXPECT(same(st, stpath(ipe(MPT(abcG3)), ipe(MPT(abcA2)))));
1424 }
1425 }
1426
1427 void
1429 {
1430 testcase("Path Find: non-XRP -> XRP");
1431 using namespace jtx;
1432 Env env = pathTestEnv();
1433 Account a1{"A1"};
1434 Account a2{"A2"};
1435 Account const g3{"G3"};
1436 Account m1{"M1"};
1437
1438 env.fund(XRP(1'000), a1, a2, g3);
1439 env.fund(XRP(11'000), m1);
1440 env.close();
1441
1442 MPTTester const eth(
1443 {.env = env,
1444 .issuer = g3,
1445 .holders = {a1, a2, m1},
1446 .pay = 1'000'000'000,
1447 .flags = kMptDexFlags});
1448
1449 AMM const ammM1(env, m1, eth(1'000'000'000), XRP(10'010));
1450
1451 STPathSet st;
1452 STAmount sa, da;
1453
1454 auto const& sendAmt = XRP(10);
1455
1456 std::tie(st, sa, da) =
1457 findPathsByElement(env, a1, a2, sendAmt, std::nullopt, ipe(MPT(eth)));
1458 BEAST_EXPECT(equal(da, sendAmt));
1459 BEAST_EXPECT(equal(sa, eth(1'000'000)));
1460 BEAST_EXPECT(same(st, stpath(ipe(xrpIssue()))));
1461 }
1462
1463 void
1465 {
1466 testcase("Path Find: non-XRP -> non-XRP, same issuanceID");
1467 using namespace jtx;
1468 {
1469 Env env = pathTestEnv();
1470 Account a1{"A1"};
1471 Account a2{"A2"};
1472 Account const a3{"A3"};
1473 Account const g1{"G1"};
1474 Account const g2{"G2"};
1475 Account m1{"M1"};
1476
1477 env.fund(XRP(11'000), m1);
1478 env.fund(XRP(1'000), a1, a2, a3, g1, g2);
1479 env.close();
1480
1481 MPTTester const hkdG1(
1482 {.env = env,
1483 .issuer = g1,
1484 .holders = {a1, m1},
1485 .pay = 5'000'000'000,
1486 .flags = kMptDexFlags});
1487
1488 MPTTester const hkdG2(
1489 {.env = env,
1490 .issuer = g2,
1491 .holders = {a2, m1},
1492 .pay = 5'000'000'000,
1493 .flags = kMptDexFlags});
1494
1495 AMM const ammM1(env, m1, hkdG1(1'000'000'000), hkdG2(1'010'000'000));
1496
1497 auto const& sendAmt = hkdG2(10'000'000);
1498 STPathSet st;
1499 STAmount sa, da;
1500 std::tie(st, sa, da) = jtx::findPaths(
1501 env, g1, a2, sendAmt, std::nullopt, hkdG1.issuanceID(), std::nullopt, std::nullopt);
1502 BEAST_EXPECT(equal(da, sendAmt));
1503 BEAST_EXPECT(equal(sa, hkdG1(10'000'000)));
1504 BEAST_EXPECT(same(st, stpath(ipe(MPT(hkdG2)))));
1505 }
1506 }
1507
1508 void
1510 {
1511 testcase("falseDryChanges");
1512
1513 using namespace jtx;
1514
1515 Env env(*this, features);
1516 env.memoize(bob_);
1517
1518 env.fund(XRP(10'000), alice_, gw_);
1519 fund(env, gw_, {carol_}, XRP(10'000), {}, Fund::Acct);
1520 auto const ammxrpPool = env.current()->fees().increment * 2;
1521 env.fund(reserve(env, 5) + ammCrtFee(env) + ammxrpPool, bob_);
1522 env.close();
1523
1524 MPTTester const eth(
1525 {.env = env, .issuer = gw_, .holders = {alice_, bob_, carol_}, .flags = kMptDexFlags});
1526
1527 MPTTester const btc(
1528 {.env = env, .issuer = gw_, .holders = {alice_, bob_, carol_}, .flags = kMptDexFlags});
1529
1530 env(pay(gw_, alice_, eth(50'000)));
1531 env(pay(gw_, bob_, btc(150'000)));
1532
1533 // Bob has _just_ slightly less than 50 xrp available
1534 // If his owner count changes, he will have more liquidity.
1535 // This is one error case to test (when Flow is used).
1536 // Computing the incoming xrp to the XRP/BTC offer will require two
1537 // recursive calls to the ETH/XRP offer. The second call will return
1538 // tecPATH_DRY, but the entire path should not be marked as dry.
1539 // This is the second error case to test (when flowV1 is used).
1540 env(offer(bob_, eth(50'000), XRP(50)));
1541 AMM const ammBob(env, bob_, ammxrpPool, btc(150'000));
1542
1543 env(pay(alice_, carol_, btc(1'000'000'000)),
1544 Path(~XRP, ~MPT(btc)),
1545 Sendmax(eth(500'000)),
1546 Txflags(tfNoRippleDirect | tfPartialPayment));
1547
1548 auto const carolBTC = env.balance(carol_, MPT(btc));
1549 BEAST_EXPECT(carolBTC > btc(0) && carolBTC < btc(50'000));
1550 }
1551
1552 void
1554 {
1555 testcase("Book Step");
1556
1557 using namespace jtx;
1558
1559 // simple MPT/IOU mix offer
1560 {
1561 auto test = [&](auto&& issue1, auto&& issue2) {
1562 Env env(*this);
1563 env.fund(XRP(30'000), alice_, bob_, carol_, gw_);
1564 env.close();
1565 auto const eth = issue1(
1566 {.env = env,
1567 .token = "ETH",
1568 .issuer = gw_,
1569 .holders = {alice_, bob_, carol_},
1570 .limit = 100'000'000});
1571 auto const btc = issue2(
1572 {.env = env,
1573 .token = "BTC",
1574 .issuer = gw_,
1575 .holders = {alice_, bob_, carol_},
1576 .limit = 100'000'000});
1577 env(pay(gw_, alice_, btc(500000)));
1578 env(pay(gw_, bob_, btc(500000)));
1579 env(pay(gw_, carol_, btc(500000)));
1580 env(pay(gw_, alice_, eth(500000)));
1581 env(pay(gw_, bob_, eth(500000)));
1582 env(pay(gw_, carol_, eth(500000)));
1583 env.close();
1584 AMM const ammBob(env, bob_, btc(100'000), eth(150'000));
1585
1586 env(pay(alice_, carol_, eth(50'000)), Path(~eth), Sendmax(btc(50'000)));
1587
1588 env.require(Balance(alice_, btc(450'000)));
1589 env.require(Balance(bob_, btc(400'000)));
1590 env.require(Balance(bob_, eth(350'000)));
1591 env.require(Balance(carol_, eth(550'000)));
1592 BEAST_EXPECT(ammBob.expectBalances(btc(150'000), eth(100'000), ammBob.tokens()));
1593 };
1595 }
1596
1597 {
1598 // simple MPT/XRP XRP/MPT offer
1599 Env env(*this, features);
1600 env.fund(XRP(10'000), gw_, alice_, bob_, carol_);
1601
1602 MPTTester const btc(
1603 {.env = env,
1604 .issuer = gw_,
1605 .holders = {alice_, bob_, carol_},
1606 .pay = 100'000,
1607 .flags = kMptDexFlags});
1608
1609 MPTTester const eth(
1610 {.env = env,
1611 .issuer = gw_,
1612 .holders = {alice_, bob_, carol_},
1613 .pay = 150'000,
1614 .flags = kMptDexFlags});
1615
1616 AMM const ammBobBtcXrp(env, bob_, btc(100'000), XRP(150));
1617 AMM const ammBobXrpEth(env, bob_, XRP(100), eth(150'000));
1618
1619 env(pay(alice_, carol_, eth(50'000)), Path(~XRP, ~MPT(eth)), Sendmax(btc(50'000)));
1620
1621 env.require(Balance(alice_, btc(50'000)));
1622 env.require(Balance(bob_, btc(0)));
1623 env.require(Balance(bob_, eth(0)));
1624 env.require(Balance(carol_, eth(200'000)));
1625 BEAST_EXPECT(
1626 ammBobBtcXrp.expectBalances(btc(150'000), XRP(100), ammBobBtcXrp.tokens()));
1627 BEAST_EXPECT(
1628 ammBobXrpEth.expectBalances(XRP(150), eth(100'000), ammBobXrpEth.tokens()));
1629 }
1630 {
1631 // simple XRP -> MPT through offer and sendmax
1632 Env env(*this, features);
1633 XRPAmount const baseFee{env.current()->fees().base};
1634 env.fund(XRP(10'000), gw_, alice_, bob_, carol_);
1635
1636 MPTTester const eth(
1637 {.env = env,
1638 .issuer = gw_,
1639 .holders = {alice_, bob_, carol_},
1640 .pay = 150'000,
1641 .flags = kMptDexFlags});
1642
1643 AMM const ammBob(env, bob_, XRP(100), eth(150'000));
1644
1645 env(pay(alice_, carol_, eth(50'000)), Path(~MPT(eth)), Sendmax(XRP(50)));
1646 BEAST_EXPECT(expectLedgerEntryRoot(env, alice_, XRP(10'000) - XRP(50) - 2 * baseFee));
1647 BEAST_EXPECT(expectLedgerEntryRoot(
1648 env, bob_, XRP(10'000) - XRP(100) - ammCrtFee(env) - baseFee));
1649 env.require(Balance(bob_, eth(0)));
1650 env.require(Balance(carol_, eth(200'000)));
1651 BEAST_EXPECT(ammBob.expectBalances(XRP(150), eth(100'000), ammBob.tokens()));
1652 }
1653 {
1654 // simple MPT -> XRP through offer and sendmax
1655 Env env(*this, features);
1656 XRPAmount const baseFee{env.current()->fees().base};
1657 env.fund(XRP(10'000), gw_, alice_, bob_, carol_);
1658
1659 MPTTester const eth(
1660 {.env = env,
1661 .issuer = gw_,
1662 .holders = {alice_, bob_, carol_},
1663 .pay = 100'000,
1664 .flags = kMptDexFlags});
1665
1666 AMM const ammBob(env, bob_, eth(100'000), XRP(150));
1667
1668 env(pay(alice_, carol_, XRP(50)), Path(~XRP), Sendmax(eth(50'000)));
1669
1670 env.require(Balance(alice_, eth(50'000)));
1671 BEAST_EXPECT(expectLedgerEntryRoot(
1672 env, bob_, XRP(10'000) - XRP(150) - ammCrtFee(env) - baseFee));
1673 env.require(Balance(bob_, eth(0)));
1674 BEAST_EXPECT(expectLedgerEntryRoot(env, carol_, XRP(10'000 + 50) - baseFee));
1675 BEAST_EXPECT(ammBob.expectBalances(eth(150'000), XRP(100), ammBob.tokens()));
1676 }
1677
1678 // test unfunded offers are removed when payment succeeds
1679 {
1680 auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) {
1681 Env env(*this, features);
1682 env.fund(XRP(10'000), alice_, bob_, carol_, gw_);
1683 env.close();
1684 auto const btc = issue1(
1685 {.env = env,
1686 .token = "BTC",
1687 .issuer = gw_,
1688 .holders = {alice_, bob_, carol_},
1689 .limit = 1000'000000});
1690 auto const eth = issue2(
1691 {.env = env,
1692 .token = "ETH",
1693 .issuer = gw_,
1694 .holders = {alice_, bob_, carol_},
1695 .limit = 1000'000000});
1696 auto const gbp = issue3(
1697 {.env = env,
1698 .token = "GBP",
1699 .issuer = gw_,
1700 .holders = {alice_, bob_, carol_},
1701 .limit = 1000'000000});
1702
1703 env(pay(gw_, alice_, btc(60'000)));
1704 env(pay(gw_, bob_, eth(200'000)));
1705 env(pay(gw_, bob_, gbp(150'000)));
1706 env(offer(bob_, btc(50'000), eth(50'000)));
1707 env(offer(bob_, btc(40'000), gbp(50'000)));
1708 env.close();
1709 AMM const ammBob(env, bob_, gbp(100'000), eth(150'000));
1710
1711 // unfund offer
1712 env(pay(bob_, gw_, gbp(50'000)));
1713 BEAST_EXPECT(isOffer(env, bob_, btc(50'000), eth(50'000)));
1714 BEAST_EXPECT(isOffer(env, bob_, btc(40'000), gbp(50'000)));
1715 env(pay(alice_, carol_, eth(50'000)),
1716 Path(~eth),
1717 Path(~gbp, ~eth),
1718 Sendmax(btc(60'000)));
1719 env.require(Balance(alice_, btc(10'000)));
1720 env.require(Balance(bob_, btc(50'000)));
1721 env.require(Balance(bob_, eth(0)));
1722 env.require(Balance(bob_, gbp(0)));
1723 env.require(Balance(carol_, eth(50'000)));
1724 // used in the payment
1725 BEAST_EXPECT(!isOffer(env, bob_, btc(50'000), eth(50'000)));
1726 // found unfunded
1727 BEAST_EXPECT(!isOffer(env, bob_, btc(40'000), gbp(50'000)));
1728 // unchanged
1729 BEAST_EXPECT(ammBob.expectBalances(gbp(100'000), eth(150'000), ammBob.tokens()));
1730 };
1732 }
1733
1734 {
1735 // test unfunded offers are removed when the payment fails.
1736 // bob makes two offers: a funded 50'000'000 ETH for 50'000'000 BTC
1737 // and an unfunded 50'000'000 GBP for 60'000'000 BTC. alice pays
1738 // carol 61'000'000 ETH with 61'000'000 BTC. alice only has
1739 // 60'000'000 BTC, so the payment will fail. The payment uses two
1740 // paths: one through bob's funded offer and one through his
1741 // unfunded offer. When the payment fails `flow` should return the
1742 // unfunded offer. This test is intentionally similar to the one
1743 // that removes unfunded offers when the payment succeeds.
1744 Env env(*this, features);
1745
1746 env.fund(XRP(10'000), bob_, carol_, gw_);
1747 env.close();
1748 // Sets rippling on, this is different from
1749 // the original test
1750 fund(env, gw_, {alice_}, XRP(10'000), {}, Fund::Acct);
1751
1752 MPTTester btc(
1753 {.env = env,
1754 .issuer = gw_,
1755 .holders = {alice_, bob_, carol_},
1756 .flags = kMptDexFlags});
1757
1758 MPTTester eth(
1759 {.env = env,
1760 .issuer = gw_,
1761 .holders = {alice_, bob_, carol_},
1762 .flags = kMptDexFlags});
1763
1764 MPTTester gbp(
1765 {.env = env,
1766 .issuer = gw_,
1767 .holders = {alice_, bob_, carol_},
1768 .flags = kMptDexFlags});
1769
1770 env(pay(gw_, alice_, btc(60'000'000)));
1771 env(pay(gw_, bob_, btc(100'000'000)));
1772 env(pay(gw_, bob_, eth(100'000'000)));
1773 env(pay(gw_, bob_, gbp(50'000'000)));
1774 env(pay(gw_, carol_, gbp(1'000'000)));
1775 env.close();
1776
1777 // This is multiplath, which generates limited # of offers
1778 AMM const ammBobBtcEth(env, bob_, btc(50'000'000), eth(50'000'000));
1779 env(offer(bob_, btc(60'000'000), gbp(50'000'000)));
1780 env(offer(carol_, btc(1'000'000'000), gbp(1'000'000)));
1781 env(offer(bob_, gbp(50'000'000), eth(50'000'000)));
1782
1783 // unfund offer
1784 env(pay(bob_, gw_, gbp(50'000'000)));
1785 BEAST_EXPECT(ammBobBtcEth.expectBalances(
1786 btc(50'000'000), eth(50'000'000), ammBobBtcEth.tokens()));
1787 BEAST_EXPECT(isOffer(env, bob_, btc(60'000'000), gbp(50'000'000)));
1788 BEAST_EXPECT(isOffer(env, carol_, btc(1'000'000'000), gbp(1'000'000)));
1789 BEAST_EXPECT(isOffer(env, bob_, gbp(50'000'000), eth(50'000'000)));
1790
1791 auto flowJournal = env.app().getLogs().journal("Flow");
1792 auto const flowResult = [&] {
1793 STAmount const deliver(eth(51'000'000));
1794 STAmount smax(btc(61'000'000));
1795 PaymentSandbox sb(env.current().get(), TapNone);
1796 STPathSet paths;
1797 auto ipe = [](MPTTester const& iss) {
1798 return STPathElement(
1800 xrpAccount(),
1801 PathAsset{iss.issuanceID()},
1802 iss.issuer());
1803 };
1804 {
1805 // BTC -> ETH
1806 STPath const p1({ipe(eth)});
1807 paths.pushBack(p1);
1808 // BTC -> GBP -> ETH
1809 STPath const p2({ipe(gbp), ipe(eth)});
1810 paths.pushBack(p2);
1811 }
1812
1813 return flow(
1814 sb,
1815 deliver,
1816 alice_,
1817 carol_,
1818 paths,
1819 false,
1820 false,
1821 true,
1823 std::nullopt,
1824 smax,
1825 std::nullopt,
1826 flowJournal);
1827 }();
1828
1829 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
1830 env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
1831 if (flowResult.removableOffers.empty())
1832 return false;
1833 Sandbox sb(&view, TapNone);
1834 for (auto const& o : flowResult.removableOffers)
1835 {
1836 if (auto ok = sb.peek(keylet::offer(o)))
1837 {
1838 offerDelete(sb, ok, flowJournal);
1839 }
1840 }
1841 sb.apply(view);
1842 return true;
1843 });
1844
1845 // used in payment, but since payment failed should be untouched
1846 BEAST_EXPECT(ammBobBtcEth.expectBalances(
1847 btc(50'000'000), eth(50'000'000), ammBobBtcEth.tokens()));
1848 BEAST_EXPECT(isOffer(env, carol_, btc(1'000'000'000), gbp(1'000'000)));
1849 // found unfunded
1850 BEAST_EXPECT(!isOffer(env, bob_, btc(60'000'000), gbp(50'000'000)));
1851 }
1852 {
1853 // Do not produce more in the forward pass than the reverse pass
1854 // This test uses a path that whose reverse pass will compute a
1855 // 500 ETH input required for a 1'000 BTC output. It sets a sendmax
1856 // of 400 ETH, so the payment engine will need to do a forward
1857 // pass. Without limits, the 400 ETH would produce 1'000 BTC in
1858 // the forward pass. This test checks that the payment produces
1859 // 1'000 BTC, as expected.
1860
1861 auto test = [&](auto&& issue1, auto&& issue2) {
1862 Env env(*this);
1863 env.fund(XRP(30'000), alice_, bob_, carol_, gw_);
1864 env.close();
1865 auto const eth = issue1(
1866 {.env = env,
1867 .token = "ETH",
1868 .issuer = gw_,
1869 .holders = {alice_, bob_, carol_},
1870 .limit = 10'000'000});
1871 auto const btc = issue2(
1872 {.env = env,
1873 .token = "BTC",
1874 .issuer = gw_,
1875 .holders = {alice_, bob_, carol_},
1876 .limit = 10'000'000});
1877
1878 env(pay(gw_, alice_, eth(1'000'000)));
1879 env(pay(gw_, bob_, btc(1'000'000)));
1880 env(pay(gw_, bob_, eth(1'000'000)));
1881 env.close();
1882
1883 AMM const ammBob(env, bob_, eth(8'000), XRPAmount{21});
1884 env(offer(bob_, drops(1), btc(1'000'000)), Txflags(tfPassive));
1885
1886 env(pay(alice_, carol_, btc(1'000)),
1887 Path(~XRP, ~btc),
1888 Sendmax(eth(400)),
1889 Txflags(tfNoRippleDirect | tfPartialPayment));
1890
1891 env.require(Balance(carol_, btc(1'000)));
1892 BEAST_EXPECT(ammBob.expectBalances(eth(8400), XRPAmount{20}, ammBob.tokens()));
1893 };
1895 }
1896 }
1897
1898 void
1900 {
1901 testcase("No Owner Fee");
1902 using namespace jtx;
1903
1904 {
1905 // payment via AMM
1906 Env env(*this, features);
1907 env.fund(XRP(1'000), gw_, alice_, bob_, carol_);
1908
1909 MPTTester const gbp(
1910 {.env = env,
1911 .issuer = gw_,
1912 .holders = {alice_, bob_, carol_},
1913 .transferFee = 25'000,
1914 .pay = 1'000'000'000'000'000,
1915 .flags = kMptDexFlags});
1916
1917 MPTTester const btc(
1918 {.env = env,
1919 .issuer = gw_,
1920 .holders = {alice_, bob_, carol_},
1921 .transferFee = 25'000,
1922 .pay = 1'000'000'000'000'000,
1923 .flags = kMptDexFlags});
1924
1925 AMM const amm(env, bob_, gbp(1'000'000'000'000'000), btc(1'000'000'000'000'000));
1926
1927 env(pay(alice_, carol_, btc(100'000'000'000'000)),
1928 Path(~MPT(btc)),
1929 Sendmax(gbp(150'000'000'000'000)),
1930 Txflags(tfNoRippleDirect | tfPartialPayment));
1931 env.close();
1932
1933 // alice buys 107.1428e12BTC with 120e12GBP and pays 25% tr fee on
1934 // 120e12GBP 1,000e12 - 120e12*1.25 = 850e12GBP
1935 env.require(Balance(alice_, gbp(850'000'000'000'000)));
1936
1937 BEAST_EXPECT(amm.expectBalances(
1938 gbp(1'120'000'000'000'000), btc(892'857'142'857'143), amm.tokens()));
1939
1940 // 25% of 85.7142e12BTC is paid in tr fee
1941 // 85.7142e12*1.25 = 107.1428e12BTC
1942 env.require(Balance(carol_, btc(1'085'714'285'714'285)));
1943 }
1944 {
1945 // Payment via offer and AMM
1946 Env env(*this, features);
1947 Account const ed("ed");
1948
1949 env.fund(XRP(1'000), gw_, alice_, bob_, carol_, ed);
1950
1951 MPTTester const gbp(
1952 {.env = env,
1953 .issuer = gw_,
1954 .holders = {alice_, bob_, carol_, ed},
1955 .transferFee = 25'000,
1956 .pay = 1'000'000'000'000'000,
1957 .flags = kMptDexFlags});
1958
1959 MPTTester const btc(
1960 {.env = env,
1961 .issuer = gw_,
1962 .holders = {alice_, bob_, carol_, ed},
1963 .transferFee = 25'000,
1964 .pay = 1'000'000'000'000'000,
1965 .flags = kMptDexFlags});
1966
1967 MPTTester const eth(
1968 {.env = env,
1969 .issuer = gw_,
1970 .holders = {alice_, bob_, carol_, ed},
1971 .transferFee = 25'000,
1972 .pay = 1'000'000'000'000'000,
1973 .flags = kMptDexFlags});
1974
1975 env(offer(ed, gbp(1'000'000'000'000'000), eth(1'000'000'000'000'000)),
1976 Txflags(tfPassive));
1977 env.close();
1978
1979 AMM const amm(env, bob_, eth(1'000'000'000'000'000), btc(1'000'000'000'000'000));
1980
1981 env(pay(alice_, carol_, btc(100'000'000'000'000)),
1982 Path(~MPT(eth), ~MPT(btc)),
1983 Sendmax(gbp(150'000'000'000'000)),
1984 Txflags(tfNoRippleDirect | tfPartialPayment));
1985 env.close();
1986
1987 // alice buys 120e12ETH with 120e12GBP via the offer
1988 // and pays 25% tr fee on 120e12GBP
1989 // 1,000e12 - 120e12*1.25 = 850e12GBP
1990 env.require(Balance(alice_, gbp(850'000'000'000'000)));
1991 // consumed offer is 120e12GBP/120e12ETH
1992 // ed doesn't pay tr fee
1993 env.require(Balance(ed, eth(880'000'000'000'000)));
1994 env.require(Balance(ed, gbp(1'120'000'000'000'000)));
1995 BEAST_EXPECT(expectOffers(
1996 env, ed, 1, {Amounts{gbp(880'000'000'000'000), eth(880'000'000'000'000)}}));
1997 // 25% on 96e12ETH is paid in tr fee 96e12*1.25 = 120e12ETH
1998 // 96e12ETH is swapped in for 87.5912e12BTC
1999 BEAST_EXPECT(amm.expectBalances(
2000 eth(1'096'000'000'000'000), btc(912'408'759'124'088), amm.tokens()));
2001 // 25% on 70.0729e12BTC is paid in tr fee 70.0729e12*1.25
2002 // = 87.5912e12BTC
2003 env.require(Balance(carol_, btc(1'070'072'992'700'729)));
2004 }
2005 {
2006 // Payment via AMM, AMM
2007 Env env(*this, features);
2008 Account const ed("ed");
2009
2010 env.fund(XRP(1'000), gw_, alice_, bob_, carol_, ed);
2011
2012 MPTTester const gbp(
2013 {.env = env,
2014 .issuer = gw_,
2015 .holders = {alice_, bob_, carol_, ed},
2016 .transferFee = 25'000,
2017 .pay = 1'000'000'000'000'000,
2018 .flags = kMptDexFlags});
2019
2020 MPTTester const btc(
2021 {.env = env,
2022 .issuer = gw_,
2023 .holders = {alice_, bob_, carol_, ed},
2024 .transferFee = 25'000,
2025 .pay = 1'000'000'000'000'000,
2026 .flags = kMptDexFlags});
2027
2028 MPTTester const eth(
2029 {.env = env,
2030 .issuer = gw_,
2031 .holders = {alice_, bob_, carol_, ed},
2032 .transferFee = 25'000,
2033 .pay = 1'000'000'000'000'000,
2034 .flags = kMptDexFlags});
2035
2036 AMM const amm1(env, bob_, gbp(1'000'000'000'000'000), eth(1'000'000'000'000'000));
2037 AMM const amm2(env, ed, eth(1'000'000'000'000'000), btc(1'000'000'000'000'000));
2038
2039 env(pay(alice_, carol_, btc(100'000'000'000'000)),
2040 Path(~MPT(eth), ~MPT(btc)),
2041 Sendmax(gbp(150'000'000'000'000)),
2042 Txflags(tfNoRippleDirect | tfPartialPayment));
2043 env.close();
2044
2045 env.require(Balance(alice_, gbp(850'000'000'000'000)));
2046
2047 // alice buys 107.1428e12ETH with 120e12GBP and pays 25% tr fee on
2048 // 120e12GBP 1,000e12 - 120e12*1.25 = 850e12GBP 120e12GBP is swapped
2049 // in for 107.1428e12ETH
2050 BEAST_EXPECT(amm1.expectBalances(
2051 gbp(1'120'000'000'000'000), eth(892'857'142'857'143), amm1.tokens()));
2052 // 25% on 85.7142e12ETH is paid in tr fee 85.7142e12*1.25 =
2053 // 107.1428e12ETH 85.7142e12ETH is swapped in for 78.9473e12BTC
2054 BEAST_EXPECT(amm2.expectBalances(
2055 eth(1'085'714'285'714'285), btc(921'052'631'578'948), amm2.tokens()));
2056
2057 // 25% on 63.1578e12BTC is paid in tr fee 63.1578e12*1.25
2058 // = 78.9473e12BTC
2059 env.require(Balance(carol_, btc(1'063'157'894'736'841)));
2060 }
2061 {
2062 // AMM offer crossing
2063 Env env(*this, features);
2064
2065 env.fund(XRP(1'000), gw_, alice_, bob_);
2066
2067 MPTTester const btc(
2068 {.env = env,
2069 .issuer = gw_,
2070 .holders = {alice_, bob_},
2071 .transferFee = 25'000,
2072 .pay = 1'100'000,
2073 .flags = kMptDexFlags});
2074
2075 MPTTester const eth(
2076 {.env = env,
2077 .issuer = gw_,
2078 .holders = {alice_, bob_},
2079 .transferFee = 25'000,
2080 .pay = 1'100'000,
2081 .flags = kMptDexFlags});
2082
2083 AMM const amm(env, bob_, btc(1'000'000), eth(1'100'000));
2084 env(offer(alice_, eth(100'000), btc(100'000)));
2085 env.close();
2086
2087 // 100e3BTC is swapped in for 100e3ETH
2088 BEAST_EXPECT(amm.expectBalances(btc(1'100'000), eth(1'000'000), amm.tokens()));
2089 // alice pays 25% tr fee on 100e3BTC 1100e3-100e3*1.25 = 975e3BTC
2090 env.require(Balance(alice_, btc(975'000)));
2091 env.require(Balance(alice_, eth(1'200'000)));
2092 BEAST_EXPECT(expectOffers(env, alice_, 0));
2093 }
2094 {
2095 // Payment via AMM with limit quality
2096 Env env(*this, features);
2097
2098 env.fund(XRP(1'000), gw_, alice_, bob_, carol_);
2099
2100 MPTTester const btc(
2101 {.env = env,
2102 .issuer = gw_,
2103 .holders = {alice_, bob_, carol_},
2104 .transferFee = 25'000,
2105 .pay = 1'000'000'000'000'000,
2106 .flags = kMptDexFlags});
2107
2108 MPTTester const gbp(
2109 {.env = env,
2110 .issuer = gw_,
2111 .holders = {alice_, bob_, carol_},
2112 .transferFee = 25'000,
2113 .pay = 1'000'000'000'000'000,
2114 .flags = kMptDexFlags});
2115
2116 AMM const amm(env, bob_, gbp(1'000'000'000'000'000), btc(1'000'000'000'000'000));
2117
2118 // requested quality limit is 100e12BTC/178.58e12GBP = 0.55997
2119 // trade quality is 100e12BTC/178.5714 = 0.55999e12
2120 env(pay(alice_, carol_, btc(100'000'000'000'000)),
2121 Path(~MPT(btc)),
2122 Sendmax(gbp(178'580'000'000'000)),
2123 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2124 env.close();
2125
2126 // alice buys 125e12BTC with 142.8571e12GBP and pays 25% tr fee
2127 // on 142.8571e12GBP
2128 // 1,000e12 - 142.8571e12*1.25 = 821.4285e12GBP
2129 env.require(Balance(alice_, gbp(821'428'571'428'571)));
2130 // 142.8571e12GBP is swapped in for 125e12BTC
2131 BEAST_EXPECT(amm.expectBalances(
2132 gbp(1'142'857'142'857'143), btc(875'000'000'000'000), amm.tokens()));
2133 // 25% on 100e12BTC is paid in tr fee
2134 // 100e12*1.25 = 125e12BTC
2135 env.require(Balance(carol_, btc(1'100'000'000'000'000)));
2136 }
2137 {
2138 // Payment via AMM with limit quality, deliver less
2139 // than requested
2140 Env env(*this, features);
2141
2142 env.fund(XRP(1'000), gw_, alice_, bob_, carol_);
2143
2144 MPTTester const btc(
2145 {.env = env,
2146 .issuer = gw_,
2147 .holders = {alice_, bob_, carol_},
2148 .transferFee = 25'000,
2149 .pay = 1'200'000'000'000'000,
2150 .flags = kMptDexFlags});
2151
2152 MPTTester const gbp(
2153 {.env = env,
2154 .issuer = gw_,
2155 .holders = {alice_, bob_, carol_},
2156 .transferFee = 25'000,
2157 .pay = 1'200'000'000'000'000,
2158 .flags = kMptDexFlags});
2159
2160 AMM const amm(env, bob_, gbp(1'000'000'000'000'000), btc(1'200'000'000'000'000));
2161
2162 // requested quality limit is 90e12BTC/120e12GBP = 0.75
2163 // trade quality is 22.5e12BTC/30e12GBP = 0.75
2164 env(pay(alice_, carol_, btc(90'000'000'000'000)),
2165 Path(~MPT(btc)),
2166 Sendmax(gbp(120'000'000'000'000)),
2167 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2168 env.close();
2169
2170 // alice buys 28.125e12BTC with 24e12GBP and pays 25% tr fee
2171 // on 24e12GBP
2172 // 1,200e12 - 24e12*1.25 =~ 1,170e12GBP
2173 env.require(Balance(alice_, gbp(1'170'000'000'000'000)));
2174 // 24e12GBP is swapped in for 28.125e12BTC
2175 BEAST_EXPECT(amm.expectBalances(
2176 gbp(1'024'000'000'000'000), btc(1'171'875'000'000'000), amm.tokens()));
2177
2178 // 25% on 22.5e12BTC is paid in tr fee
2179 // 22.5*1.25 = 28.125e12BTC
2180 env.require(Balance(carol_, btc(1'222'500'000'000'000)));
2181 }
2182 {
2183 // Payment via offer and AMM with limit quality, deliver less
2184 // than requested
2185 Env env(*this, features);
2186 Account const ed("ed");
2187
2188 env.fund(XRP(1'000), gw_, alice_, bob_, carol_, ed);
2189
2190 MPTTester const btc(
2191 {.env = env,
2192 .issuer = gw_,
2193 .holders = {alice_, bob_, carol_, ed},
2194 .transferFee = 25'000,
2195 .pay = 1'400'000'000'000'000,
2196 .flags = kMptDexFlags});
2197
2198 MPTTester const gbp(
2199 {.env = env,
2200 .issuer = gw_,
2201 .holders = {alice_, bob_, carol_, ed},
2202 .transferFee = 25'000,
2203 .pay = 1'400'000'000'000'000,
2204 .flags = kMptDexFlags});
2205
2206 MPTTester const eth(
2207 {.env = env,
2208 .issuer = gw_,
2209 .holders = {alice_, bob_, carol_, ed},
2210 .transferFee = 25'000,
2211 .pay = 1'400'000'000'000'000,
2212 .flags = kMptDexFlags});
2213
2214 env(offer(ed, gbp(1'000'000'000'000'000), eth(1'000'000'000'000'000)),
2215 Txflags(tfPassive));
2216 env.close();
2217
2218 AMM const amm(env, bob_, eth(1'000'000'000'000'000), btc(1'400'000'000'000'000));
2219
2220 // requested quality limit is 95e12BTC/140e12GBP = 0.6785
2221 // trade quality is 59.7321e12BTC/88.0262e12GBP = 0.6785
2222 env(pay(alice_, carol_, btc(95'000'000'000'000)),
2223 Path(~MPT(eth), ~MPT(btc)),
2224 Sendmax(gbp(140'000'000'000'000)),
2225 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2226 env.close();
2227
2228 // alice buys 70.4210e12ETH with 70.4210e12GBP via the offer
2229 // and pays 25% tr fee on 70.4210e12GBP
2230 // 1,400e12 - 70.4210e12*1.25 = 1400e12 - 88.0262e12 =
2231 // 1311.9736e12GBP
2232 env.require(Balance(alice_, gbp(1'311'973'684'210'525)));
2233 // ed doesn't pay tr fee, the balances reflect consumed offer
2234 // 70.4210e12GBP/70.4210e12ETH
2235 env.require(Balance(ed, eth(1'329'578'947'368'420)));
2236 env.require(Balance(ed, gbp(1'470'421'052'631'580)));
2237 BEAST_EXPECT(expectOffers(
2238 env, ed, 1, {Amounts{gbp(929'578'947'368'420), eth(929'578'947'368'420)}}));
2239 // 25% on 56.3368e12ETH is paid in tr fee 56.3368e12*1.25
2240 // = 70.4210e12ETH 56.3368e12ETH is swapped in for 74.6651e12BTC
2241 BEAST_EXPECT(amm.expectBalances(
2242 eth(1'056'336'842'105'264), btc(1'325'334'821'428'571), amm.tokens()));
2243
2244 // 25% on 59.7321e12BTC is paid in tr fee 59.7321e12*1.25
2245 // = 74.6651e12BTC
2246 env.require(Balance(carol_, btc(1'459'732'142'857'143)));
2247 }
2248 {
2249 // Payment via AMM and offer with limit quality, deliver less
2250 // than requested
2251 Env env(*this, features);
2252 Account const ed("ed");
2253
2254 env.fund(XRP(1'000), gw_, alice_, bob_, carol_, ed);
2255
2256 MPTTester const btc(
2257 {.env = env,
2258 .issuer = gw_,
2259 .holders = {alice_, bob_, carol_, ed},
2260 .transferFee = 25'000,
2261 .pay = 1'400'000'000'000'000,
2262 .flags = kMptDexFlags});
2263
2264 MPTTester const gbp(
2265 {.env = env,
2266 .issuer = gw_,
2267 .holders = {alice_, bob_, carol_, ed},
2268 .transferFee = 25'000,
2269 .pay = 1'400'000'000'000'000,
2270 .flags = kMptDexFlags});
2271
2272 MPTTester const eth(
2273 {.env = env,
2274 .issuer = gw_,
2275 .holders = {alice_, bob_, carol_, ed},
2276 .transferFee = 25'000,
2277 .pay = 1'400'000'000'000'000,
2278 .flags = kMptDexFlags});
2279
2280 AMM const amm(env, bob_, gbp(1'000'000'000'000'000), eth(1'000'000'000'000'000));
2281
2282 env(offer(ed, eth(1'000'000'000'000'000), btc(1'400'000'000'000'000)),
2283 Txflags(tfPassive));
2284 env.close();
2285
2286 // requested quality limit is 95e12BTC/140e12GBP = 0.6785
2287 // trade quality is 47.7857e12BTC/70.4210e12GBP = 0.6785
2288 env(pay(alice_, carol_, btc(95'000'000'000'000)),
2289 Path(~MPT(eth), ~MPT(btc)),
2290 Sendmax(gbp(140'000'000'000'000)),
2291 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2292 env.close();
2293
2294 // alice buys 53.3322e12ETH with 56.3368e12GBP via the amm
2295 // and pays 25% tr fee on 56.3368e12GBP
2296 // 1,400e12 - 56.3368e12*1.25 = 1400e12 - 70.4210e12 =
2297 // 1329.5789e12GBP
2298 env.require(Balance(alice_, gbp(1'329'578'947'368'420)));
2301 // 56.3368e12GBP is swapped in for 53.3322e12ETH
2302 BEAST_EXPECT(amm.expectBalances(
2303 gbp(1'056'336'842'105'264), eth(946'667'729'591'836), amm.tokens()));
2304
2305 // 25% on 42.6658e12ETH is paid in tr fee 42.6658e12*1.25
2306 // = 53.3322e12ETH 42.6658e12ETH/59.7321e12BTC
2307 env.require(Balance(ed, btc(1'340'267'857'142'857)));
2308 env.require(Balance(ed, eth(1'442'665'816'326'531)));
2309 BEAST_EXPECT(expectOffers(
2310 env, ed, 1, {Amounts{eth(957'334'183'673'469), btc(1'340'267'857'142'857)}}));
2311 // 25% on 47.7857e12BTC is paid in tr fee 47.7857e12*1.25
2312 // = 59.7321e12BTC
2313 env.require(Balance(carol_, btc(1'447'785714285714)));
2314 }
2315 {
2316 // Payment via AMM, AMM with limit quality, deliver less
2317 // than requested
2318 Env env(*this, features);
2319 Account const ed("ed");
2320
2321 env.fund(XRP(1'000), gw_, alice_, bob_, carol_, ed);
2322
2323 MPTTester const btc(
2324 {.env = env,
2325 .issuer = gw_,
2326 .holders = {alice_, bob_, carol_, ed},
2327 .transferFee = 25'000,
2328 .pay = 1'400'000'000'000'000,
2329 .flags = kMptDexFlags});
2330
2331 MPTTester const gbp(
2332 {.env = env,
2333 .issuer = gw_,
2334 .holders = {alice_, bob_, carol_, ed},
2335 .transferFee = 25'000,
2336 .pay = 1'400'000'000'000'000,
2337 .flags = kMptDexFlags});
2338
2339 MPTTester const eth(
2340 {.env = env,
2341 .issuer = gw_,
2342 .holders = {alice_, bob_, carol_, ed},
2343 .transferFee = 25'000,
2344 .pay = 1'400'000'000'000'000,
2345 .flags = kMptDexFlags});
2346
2347 AMM const amm1(env, bob_, gbp(1'000'000'000'000'000), eth(1'000'000'000'000'000));
2348 AMM const amm2(env, ed, eth(1'000'000'000'000'000), btc(1'400'000'000'000'000));
2349
2350 // requested quality limit is 90e12BTC/145e12GBP = 0.6206
2351 // trade quality is 66.7432e12BTC/107.5308e12GBP = 0.6206
2352 env(pay(alice_, carol_, btc(90'000'000'000'000)),
2353 Path(~MPT(eth), ~MPT(btc)),
2354 Sendmax(gbp(145'000'000'000'000)),
2355 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2356 env.close();
2357
2358 // alice buys 53.3322e12ETH with 107.5308e12GBP
2359 // 25% on 86.0246e12GBP is paid in tr fee
2360 // 1,400e12 - 86.0246e12*1.25 = 1400e12 - 107.5308e12 =
2361 // 1229.4691e12GBP
2362 env.require(Balance(alice_, gbp(1'292'469'135'802'465)));
2363 // 86.0246e12GBP is swapped in for 79.2106e12ETH
2364 BEAST_EXPECT(amm1.expectBalances(
2365 gbp(1'086'024'691'358'028), eth(920'789'377'955'618), amm1.tokens()));
2366 // 25% on 63.3684e12ETH is paid in tr fee 63.3684e12*1.25
2367 // = 79.2106e12ETH 63.3684e12ETH is swapped in for 83.4291e12BTC
2368 BEAST_EXPECT(amm2.expectBalances(
2369 eth(1'063'368'497'635'505), btc(1'316'570'881'226'053), amm2.tokens()));
2370
2371 // 25% on 66.7432e12BTC is paid in tr fee 66.7432e12*1.25
2372 // = 83.4291e12BTC
2373 env.require(Balance(carol_, btc(1'466'743'295'019'157)));
2374 }
2375 {
2376 // Payment by the issuer via AMM, AMM with limit quality,
2377 // deliver less than requested
2378 Env env(*this, features);
2379
2380 env.fund(XRP(1'000), gw_, alice_, bob_, carol_);
2381
2382 MPTTester const btc(
2383 {.env = env,
2384 .issuer = gw_,
2385 .holders = {alice_, bob_, carol_},
2386 .transferFee = 25'000,
2387 .pay = 1'400'000'000'000'000,
2388 .flags = kMptDexFlags});
2389
2390 MPTTester const gbp(
2391 {.env = env,
2392 .issuer = gw_,
2393 .holders = {alice_, bob_, carol_},
2394 .transferFee = 25'000,
2395 .pay = 1'400'000'000'000'000,
2396 .flags = kMptDexFlags});
2397
2398 MPTTester const eth(
2399 {.env = env,
2400 .issuer = gw_,
2401 .holders = {alice_, bob_, carol_},
2402 .transferFee = 25'000,
2403 .pay = 1'400'000'000'000'000,
2404 .flags = kMptDexFlags});
2405
2406 AMM const amm1(env, alice_, gbp(1'000'000'000'000'000), eth(1'000'000'000'000'000));
2407 AMM const amm2(env, bob_, eth(1'000'000'000'000'000), btc(1'400'000'000'000'000));
2408
2409 // requested quality limit is 90e12BTC/120e12GBP = 0.75
2410 // trade quality is 81.1111e12BTC/108.1481e12GBP = 0.75
2411 env(pay(gw_, carol_, btc(90'000'000'000'000)),
2412 Path(~MPT(eth), ~MPT(btc)),
2413 Sendmax(gbp(120'000'000'000'000)),
2414 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2415 env.close();
2416
2417 // 108.1481e12GBP is swapped in for 97.5935e12ETH
2418 BEAST_EXPECT(amm1.expectBalances(
2419 gbp(1'108'148'148'148'150), eth(902'406'417'112'298), amm1.tokens()));
2420 // 25% on 78.0748e12ETH is paid in tr fee 78.0748e12*1.25
2421 // = 97.5935e12ETH 78.0748e12ETH is swapped in for 101.3888e12BTC
2422 BEAST_EXPECT(amm2.expectBalances(
2423 eth(1'078'074'866'310'161), btc(1'298'611'111'111'111), amm2.tokens()));
2424
2425 // 25% on 81.1111e12BTC is paid in tr fee 81.1111e12*1.25 =
2426 // 101.3888e12BTC
2427 env.require(Balance(carol_, btc(1'481'111'111'111'111)));
2428 }
2429 }
2430
2431 void
2433 {
2434 // Single path with amm, offer, and limit quality. The quality limit
2435 // is such that the first offer should be taken but the second
2436 // should not. The total amount delivered should be the sum of the
2437 // two offers and sendMax should be more than the first offer.
2438 testcase("limitQuality");
2439 using namespace jtx;
2440
2441 {
2442 Env env(*this);
2443 env.fund(XRP(10'000), gw_, alice_, bob_, carol_);
2444
2445 MPTTester const eth(
2446 {.env = env,
2447 .issuer = gw_,
2448 .holders = {alice_, bob_, carol_},
2449 .pay = 2'000'000,
2450 .flags = kMptDexFlags});
2451
2452 AMM const ammBob(env, bob_, XRP(1'000), eth(1'050'000));
2453 env(offer(bob_, XRP(100), eth(50'000)));
2454
2455 env(pay(alice_, carol_, eth(100'000)),
2456 Path(~MPT(eth)),
2457 Sendmax(XRP(100)),
2458 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2459
2460 BEAST_EXPECT(ammBob.expectBalances(XRP(1'050), eth(1'000'000), ammBob.tokens()));
2461 env.require(Balance(carol_, eth(2'050'000)));
2462 BEAST_EXPECT(expectOffers(env, bob_, 1, {{{XRP(100), eth(50'000)}}}));
2463 }
2464 }
2465
2466 void
2468 {
2469 testcase("Circular XRP");
2470
2471 using namespace jtx;
2472
2473 // Payment path starting with XRP
2474 {
2475 auto test = [&](auto&& issue1, auto&& issue2) {
2476 Env env(*this);
2477 env.fund(XRP(30'000), alice_, bob_, gw_);
2478 env.close();
2479 auto const eth = issue1(
2480 {.env = env,
2481 .token = "ETH",
2482 .issuer = gw_,
2483 .holders = {alice_, bob_},
2484 .limit = 2000'000});
2485 auto const btc = issue2(
2486 {.env = env,
2487 .token = "BTC",
2488 .issuer = gw_,
2489 .holders = {alice_, bob_},
2490 .limit = 2000'000});
2491
2492 env(pay(gw_, alice_, btc(200'000)));
2493 env(pay(gw_, bob_, btc(200'000)));
2494 env(pay(gw_, alice_, eth(200'000)));
2495 env(pay(gw_, bob_, eth(200'000)));
2496 env.close();
2497
2498 AMM const ammAliceXrpBtc(env, alice_, XRP(100), btc(101'000));
2499 AMM const ammAliceXrpEth(env, alice_, XRP(100), eth(101'000));
2500 env(pay(alice_, bob_, eth(1'000)),
2501 Path(~btc, ~XRP, ~eth),
2502 Sendmax(XRP(1)),
2503 Txflags(tfNoRippleDirect),
2505 };
2507 }
2508
2509 // Payment path ending with XRP
2510 {
2511 auto test = [&](auto&& issue1, auto&& issue2) {
2512 Env env(*this);
2513 env.fund(XRP(30'000), alice_, bob_, gw_);
2514 env.close();
2515 auto const eth = issue1(
2516 {.env = env,
2517 .token = "ETH",
2518 .issuer = gw_,
2519 .holders = {alice_, bob_},
2520 .limit = 2000'000});
2521 auto const btc = issue2(
2522 {.env = env,
2523 .token = "BTC",
2524 .issuer = gw_,
2525 .holders = {alice_, bob_},
2526 .limit = 2000'000});
2527
2528 env(pay(gw_, alice_, btc(200'000)));
2529 env(pay(gw_, bob_, btc(200'000)));
2530 env(pay(gw_, alice_, eth(200'000)));
2531 env(pay(gw_, bob_, eth(200'000)));
2532 env.close();
2533
2534 AMM const ammAliceXrpBtc(env, alice_, XRP(100), btc(100'000));
2535 AMM const ammAliceXrpEth(env, alice_, XRP(100), eth(100'000));
2536 // ETH -> //XRP -> //BTC ->XRP
2537 env(pay(alice_, bob_, XRP(1)),
2538 Path(~XRP, ~btc, ~XRP),
2539 Sendmax(eth(1'000)),
2540 Txflags(tfNoRippleDirect),
2542 };
2544 }
2545
2546 // Payment where loop is formed in the middle of the path, not
2547 // on an endpoint
2548 {
2549 auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) {
2550 Env env(*this);
2551 env.fund(XRP(10'000), gw_, alice_, bob_);
2552 env.close();
2553 auto const eth = issue1(
2554 {.env = env,
2555 .token = "ETH",
2556 .issuer = gw_,
2557 .holders = {alice_, bob_},
2558 .limit = 2000'000});
2559 auto const btc = issue2(
2560 {.env = env,
2561 .token = "BTC",
2562 .issuer = gw_,
2563 .holders = {alice_, bob_},
2564 .limit = 2000'000});
2565 auto const jpy = issue2(
2566 {.env = env,
2567 .token = "JPY",
2568 .issuer = gw_,
2569 .holders = {alice_, bob_},
2570 .limit = 2000'000});
2571
2572 env(pay(gw_, alice_, btc(200'000)));
2573 env(pay(gw_, bob_, btc(200'000)));
2574 env(pay(gw_, alice_, eth(200'000)));
2575 env(pay(gw_, bob_, eth(200'000)));
2576 env(pay(gw_, alice_, jpy(200'000)));
2577 env(pay(gw_, bob_, jpy(200'000)));
2578 env.close();
2579
2580 AMM const ammAliceXrpBtc(env, alice_, XRP(100), btc(100'000));
2581 AMM const ammAliceXrpEth(env, alice_, XRP(100), eth(100'000));
2582 AMM const ammAliceXrpJpy(env, alice_, XRP(100), jpy(100'000));
2583
2584 env(pay(alice_, bob_, jpy(1'000)),
2585 Path(~XRP, ~eth, ~XRP, ~jpy),
2586 Sendmax(btc(1'000)),
2587 Txflags(tfNoRippleDirect),
2589 };
2591 }
2592 }
2593
2594 void
2596 {
2597 testcase("Step Limit");
2598
2599 using namespace jtx;
2600 {
2601 Env env(*this, features);
2602 auto const dan = Account("dan");
2603 auto const ed = Account("ed");
2604
2605 env.fund(XRP(100'000'000), gw_, alice_, bob_, carol_, dan, ed);
2606
2607 MPTTester const btc(
2608 {.env = env, .issuer = gw_, .holders = {bob_, dan, ed}, .flags = kMptDexFlags});
2609
2610 env(pay(gw_, ed, btc(11'000'000'000'000)));
2611 env(pay(gw_, bob_, btc(1'000'000'000'000)));
2612 env(pay(gw_, dan, btc(1'000'000'000'000)));
2613
2614 nOffers(env, 2'000, bob_, XRP(1), btc(1'000'000'000'000));
2615 nOffers(env, 1, dan, XRP(1), btc(1'000'000'000'000));
2616 AMM const ammEd(env, ed, XRP(9), btc(11'000'000'000'000));
2617
2618 // Alice offers to buy 1000 XRP for 1000e12 BTC. She takes Bob's
2619 // first offer, removes 999 more as unfunded, then hits the step
2620 // limit.
2621 env(offer(alice_, btc(1'000'000'000'000'000), XRP(1'000)));
2622 env.require(Balance(alice_, btc(2'050'125'257'867)));
2623 env.require(Owners(alice_, 2));
2624 env.require(Balance(bob_, btc(0)));
2625 env.require(Owners(bob_, 1'001));
2626 env.require(Balance(dan, btc(1'000'000'000'000)));
2627 env.require(Owners(dan, 2));
2628
2629 // Carol offers to buy 1000 XRP for 1000e12 BTC. She removes Bob's
2630 // next 1000 offers as unfunded and hits the step limit.
2631 env(offer(carol_, btc(1'000'000'000'000'000), XRP(1'000)));
2632 env.require(Balance(carol_, MPT(btc)(kNone)));
2633 env.require(Owners(carol_, 1));
2634 env.require(Balance(bob_, btc(0)));
2635 env.require(Owners(bob_, 1));
2636 env.require(Balance(dan, btc(1'000'000'000'000)));
2637 env.require(Owners(dan, 2));
2638 }
2639
2640 // MPT/IOU, similar to the case above
2641 {
2642 Env env(*this, features);
2643 auto const dan = Account("dan");
2644 auto const ed = Account("ed");
2645
2646 env.fund(XRP(100'000), gw_, alice_, bob_, carol_, dan, ed);
2647 env.close();
2648
2649 MPTTester const usd(
2650 {.env = env,
2651 .issuer = gw_,
2652 .holders = {alice_, bob_, carol_, dan, ed},
2653 .pay = 10000'000'000,
2654 .flags = kMptDexFlags});
2655
2656 env.trust(BTC(11'000'000'000'000), ed);
2657 env(pay(gw_, ed, BTC(11'000'000'000'000)));
2658 env.trust(BTC(1'000'000'000'000), bob_);
2659 env(pay(gw_, bob_, BTC(1'000'000'000'000)));
2660 env.trust(BTC(1'000'000'000'000), dan);
2661 env(pay(gw_, dan, BTC(1'000'000'000'000)));
2662 env.close();
2663
2664 nOffers(env, 2'000, bob_, usd(1000000), BTC(1'000'000'000'000));
2665 nOffers(env, 1, dan, usd(1000000), BTC(1'000'000'000'000));
2666 AMM const ammEd(env, ed, usd(9000000), BTC(11'000'000'000'000));
2667 env(offer(alice_, BTC(1'000'000'000'000'000), usd(1'000000000)));
2668
2669 env.require(Balance(alice_, STAmount{BTC, UINT64_C(2050125257867'587), -3}));
2670 env.require(Owners(alice_, 3));
2671 env.require(Balance(bob_, BTC(0)));
2672 env.require(Owners(bob_, 1'002));
2673 env.require(Balance(dan, BTC(1000000000000)));
2674 env.require(Owners(dan, 3));
2675 }
2676
2677 // IOU/MPT, similar to the case above
2678 {
2679 Env env(*this, features);
2680 auto const dan = Account("dan");
2681 auto const ed = Account("ed");
2682
2683 env.fund(XRP(100'000), gw_, alice_, bob_, carol_, dan, ed);
2684 env.close();
2685
2686 env.trust(USD(10000'000'000), alice_);
2687 env(pay(gw_, alice_, USD(10000'000'000)));
2688 env.trust(USD(10000'000'000), bob_);
2689 env(pay(gw_, bob_, USD(10000'000'000)));
2690 env.trust(USD(10000'000'000), carol_);
2691 env(pay(gw_, carol_, USD(10000'000'000)));
2692 env.trust(USD(10000'000'000), dan);
2693 env(pay(gw_, dan, USD(10000'000'000)));
2694 env.trust(USD(10000'000'000), ed);
2695 env(pay(gw_, ed, USD(10000'000'000)));
2696 env.close();
2697
2698 MPTTester const btc(
2699 {.env = env, .issuer = gw_, .holders = {bob_, dan, ed}, .flags = kMptDexFlags});
2700
2701 env(pay(gw_, ed, btc(11'000'000'000'000)));
2702 env(pay(gw_, bob_, btc(1'000'000'000'000)));
2703 env(pay(gw_, dan, btc(1'000'000'000'000)));
2704 env.close();
2705
2706 nOffers(env, 2'000, bob_, USD(1000000), btc(1'000'000'000'000));
2707 nOffers(env, 1, dan, USD(1000000), btc(1'000'000'000'000));
2708 AMM const ammEd(env, ed, USD(9000000), btc(11'000'000'000'000));
2709 env(offer(alice_, btc(1'000'000'000'000'000), USD(1'000000000)));
2710
2711 env.require(Balance(alice_, btc(2050125628933)));
2712 env.require(Owners(alice_, 3));
2713 env.require(Balance(bob_, btc(0)));
2714 env.require(Owners(bob_, 1'002));
2715 env.require(Balance(dan, btc(1000000000000)));
2716 env.require(Owners(dan, 3));
2717 }
2718
2719 // MPT/MPT, similar to the case above
2720 {
2721 Env env(*this, features);
2722 auto const dan = Account("dan");
2723 auto const ed = Account("ed");
2724
2725 env.fund(XRP(100'000), gw_, alice_, bob_, carol_, dan, ed);
2726 env.close();
2727
2728 MPTTester const btc(
2729 {.env = env, .issuer = gw_, .holders = {bob_, dan, ed}, .flags = kMptDexFlags});
2730 MPTTester const usd(
2731 {.env = env,
2732 .issuer = gw_,
2733 .holders = {alice_, bob_, carol_, dan, ed},
2734 .pay = 10000'000'000,
2735 .flags = kMptDexFlags});
2736
2737 env(pay(gw_, ed, btc(11'000'000'000'000)));
2738 env(pay(gw_, bob_, btc(1'000'000'000'000)));
2739 env(pay(gw_, dan, btc(1'000'000'000'000)));
2740 env.close();
2741
2742 nOffers(env, 2'000, bob_, usd(1000000), btc(1'000'000'000'000));
2743 nOffers(env, 1, dan, usd(1000000), btc(1'000'000'000'000));
2744 AMM const ammEd(env, ed, usd(9000000), btc(11'000'000'000'000));
2745 env(offer(alice_, btc(1'000'000'000'000'000), usd(1'000000000)));
2746
2747 env.require(Balance(alice_, btc(2050125257867)));
2748 env.require(Owners(alice_, 3));
2749 env.require(Balance(bob_, btc(0)));
2750 env.require(Owners(bob_, 1'002));
2751 env.require(Balance(dan, btc(1000000000000)));
2752 env.require(Owners(dan, 3));
2753 }
2754 }
2755
2756 void
2758 {
2759 testcase("Convert all of an asset using DeliverMin");
2760
2761 using namespace jtx;
2762
2763 {
2764 Env env(*this, features);
2765 fund(env, gw_, {alice_, bob_, carol_}, XRP(10'000));
2766
2767 MPTTester const btc(
2768 {.env = env,
2769 .issuer = gw_,
2770 .holders = {alice_, bob_, carol_},
2771 .flags = kMptDexFlags});
2772
2773 env(pay(alice_, bob_, btc(10'000)), DeliverMin(btc(10'000)), Ter(temBAD_AMOUNT));
2774 env(pay(alice_, bob_, btc(10'000)),
2775 DeliverMin(btc(-5'000)),
2776 Txflags(tfPartialPayment),
2778 env(pay(alice_, bob_, btc(10'000)),
2779 DeliverMin(XRP(5)),
2780 Txflags(tfPartialPayment),
2782 env(pay(alice_, bob_, btc(10'000)),
2783 DeliverMin(btc(5'000)),
2784 Txflags(tfPartialPayment),
2785 Ter(tecPATH_DRY));
2786 env(pay(alice_, bob_, btc(10'000)),
2787 DeliverMin(btc(15'000)),
2788 Txflags(tfPartialPayment),
2790 env(pay(gw_, carol_, btc(50'000)));
2791 AMM const ammCarol(env, carol_, XRP(10), btc(15'000));
2792 env(pay(alice_, bob_, btc(10'000)),
2793 Paths(XRP),
2794 DeliverMin(btc(7'000)),
2795 Txflags(tfPartialPayment),
2796 Sendmax(XRP(5)),
2798 env.require(
2799 Balance(alice_, drops(10'000'000'000 - (3 * env.current()->fees().base.drops()))));
2800 env.require(Balance(bob_, drops(10'000'000'000 - env.current()->fees().base.drops())));
2801 }
2802
2803 {
2804 Env env(*this, features);
2805 fund(env, gw_, {alice_, bob_}, XRP(10'000));
2806
2807 MPTTester const btc(
2808 {.env = env, .issuer = gw_, .holders = {alice_, bob_}, .flags = kMptDexFlags});
2809
2810 env(pay(gw_, bob_, btc(1'100'000)));
2811 AMM const ammBob(env, bob_, XRP(1'000), btc(1'100'000));
2812 env(pay(alice_, alice_, btc(10'000'000)),
2813 Paths(XRP),
2814 DeliverMin(btc(100'000)),
2815 Txflags(tfPartialPayment),
2816 Sendmax(XRP(100)));
2817 env.require(Balance(alice_, btc(100'000)));
2818 }
2819
2820 // IOU/MPT mix, similar to the above case
2821 {
2822 auto test = [&](auto&& issue1, auto&& issue2) {
2823 Env env(*this);
2824 env.fund(XRP(30'000), alice_, bob_, carol_, gw_);
2825 env.close();
2826 auto const usd = issue1(
2827 {.env = env,
2828 .token = "USD",
2829 .issuer = gw_,
2830 .holders = {alice_, bob_, carol_},
2831 .limit = 3000'000});
2832 auto const btc = issue2(
2833 {.env = env,
2834 .token = "BTC",
2835 .issuer = gw_,
2836 .holders = {alice_, bob_, carol_},
2837 .limit = 1'000'000});
2838
2839 env(pay(gw_, alice_, usd(10'000)));
2840 env(pay(gw_, bob_, usd(10'000)));
2841 env(pay(gw_, bob_, btc(1'200)));
2842 env.close();
2843
2844 AMM const ammBob(env, bob_, usd(1'000), btc(1'100));
2845 env(pay(alice_, alice_, btc(10'000)),
2846 Paths(usd),
2847 DeliverMin(btc(100)),
2848 Txflags(tfPartialPayment),
2849 Sendmax(usd(100)));
2850 env.require(Balance(alice_, btc(100)));
2851 };
2853 }
2854
2855 {
2856 Env env(*this, features);
2857 fund(env, gw_, {alice_, bob_, carol_}, XRP(10'000));
2858
2859 MPTTester const btc(
2860 {.env = env, .issuer = gw_, .holders = {bob_, carol_}, .flags = kMptDexFlags});
2861
2862 env(pay(gw_, bob_, btc(1'200'000)));
2863 AMM const ammBob(env, bob_, XRP(5'500), btc(1'200'000));
2864 env(pay(alice_, carol_, btc(10'000'000)),
2865 Paths(XRP),
2866 DeliverMin(btc(200'000)),
2867 Txflags(tfPartialPayment),
2868 Sendmax(XRP(1'000)),
2870 env(pay(alice_, carol_, btc(10'000'000)),
2871 Paths(XRP),
2872 DeliverMin(btc(200'000)),
2873 Txflags(tfPartialPayment),
2874 Sendmax(XRP(1'100)));
2875 BEAST_EXPECT(ammBob.expectBalances(XRP(6'600), btc(1'000'000), ammBob.tokens()));
2876 env.require(Balance(carol_, btc(200'000)));
2877 }
2878
2879 // IOU/MPT mix, similar to the above case
2880 {
2881 auto test = [&](auto&& issue1, auto&& issue2) {
2882 Env env(*this);
2883 env.fund(XRP(30'000), alice_, bob_, carol_, gw_);
2884 env.close();
2885 auto const usd = issue1(
2886 {.env = env,
2887 .token = "USD",
2888 .issuer = gw_,
2889 .holders = {alice_, bob_, carol_},
2890 .limit = 3000'000});
2891 auto const btc = issue2(
2892 {.env = env,
2893 .token = "BTC",
2894 .issuer = gw_,
2895 .holders = {alice_, bob_, carol_},
2896 .limit = 1'000'000});
2897
2898 env(pay(gw_, alice_, usd(100'000)));
2899 env(pay(gw_, bob_, usd(100'000)));
2900 env(pay(gw_, carol_, usd(100'000)));
2901
2902 env(pay(gw_, bob_, btc(1'200)));
2903 env.close();
2904
2905 AMM const ammBob(env, bob_, usd(5'500), btc(1'200));
2906 env(pay(alice_, carol_, btc(10'000)),
2907 Paths(usd),
2908 DeliverMin(btc(200)),
2909 Txflags(tfPartialPayment),
2910 Sendmax(usd(1'000)),
2912 env(pay(alice_, carol_, btc(10'000)),
2913 Paths(usd),
2914 DeliverMin(btc(200)),
2915 Txflags(tfPartialPayment),
2916 Sendmax(usd(1'100)));
2917 BEAST_EXPECT(ammBob.expectBalances(usd(6'600), btc(1'000), ammBob.tokens()));
2918 env.require(Balance(carol_, btc(200)));
2919 };
2921 }
2922
2923 {
2924 auto const dan = Account("dan");
2925 Env env(*this, features);
2926 fund(env, gw_, {alice_, bob_, carol_, dan}, XRP(10'000));
2927
2928 MPTTester const btc(
2929 {.env = env, .issuer = gw_, .holders = {bob_, carol_, dan}, .flags = kMptDexFlags});
2930
2931 env(pay(gw_, bob_, btc(100'000'000)));
2932 env(pay(gw_, dan, btc(1'100'000'000)));
2933 env(offer(bob_, XRP(100), btc(100'000'000)));
2934 env(offer(bob_, XRP(1'000), btc(100'000'000)));
2935 AMM const ammDan(env, dan, XRP(1'000), btc(1'100'000'000));
2936
2937 env(pay(alice_, carol_, btc(10'000'000'000)),
2938 Paths(XRP),
2939 DeliverMin(btc(200'000'000)),
2940 Txflags(tfPartialPayment),
2941 Sendmax(XRPAmount(200'000'001)));
2942 env.require(Balance(bob_, btc(0)));
2943 env.require(Balance(carol_, btc(200'000'000)));
2944 BEAST_EXPECT(
2945 ammDan.expectBalances(XRPAmount{1'100'000'001}, btc(1000'000000), ammDan.tokens()));
2946 }
2947 }
2948
2949 void
2951 {
2952 testcase("Payment");
2953
2954 using namespace jtx;
2955 Account const becky{"becky"};
2956
2957 Env env(*this, features);
2958 fund(env, gw_, {alice_, becky}, XRP(5'000));
2959
2960 MPTTester const btc(
2961 {.env = env, .issuer = gw_, .holders = {alice_, becky}, .flags = kMptDexFlags});
2962
2963 env(pay(gw_, alice_, btc(500'000)));
2964 env.close();
2965
2966 AMM const ammAlice(env, alice_, XRP(100), btc(140'000));
2967
2968 // becky pays herself BTC (10'000) by consuming part of alice's offer.
2969 // Make sure the payment works if PaymentAuth is not involved.
2970 env(pay(becky, becky, btc(10'000)), Path(~MPT(btc)), Sendmax(XRP(10)));
2971 env.close();
2972 BEAST_EXPECT(
2973 ammAlice.expectBalances(XRPAmount(107'692'308), btc(130'000), ammAlice.tokens()));
2974
2975 // becky decides to require authorization for deposits.
2976 env(fset(becky, asfDepositAuth));
2977 env.close();
2978
2979 // becky pays herself again.
2980 env(pay(becky, becky, btc(10'000)), Path(~MPT(btc)), Sendmax(XRP(10)), Ter(tesSUCCESS));
2981
2982 env.close();
2983 }
2984
2985 void
2987 {
2988 // Exercise MPT payments and non-direct XRP payments to an account
2989 // that has the lsfDepositAuth flag set.
2990 testcase("Pay MPT");
2991
2992 using namespace jtx;
2993
2994 Env env(*this);
2995
2996 fund(env, gw_, {alice_, bob_, carol_}, XRP(10'000));
2997
2998 MPTTester btc(
2999 {.env = env, .issuer = gw_, .holders = {alice_, bob_, carol_}, .flags = kMptDexFlags});
3000
3001 env(pay(gw_, alice_, btc(150'000)));
3002 env(pay(gw_, carol_, btc(150'000)));
3003 AMM const ammCarol(env, carol_, btc(100'000), XRPAmount(101));
3004
3005 env(pay(alice_, bob_, btc(50'000)));
3006 env.close();
3007
3008 // bob sets the lsfDepositAuth flag.
3009 env(fset(bob_, asfDepositAuth), Require(Flags(bob_, asfDepositAuth)));
3010 env.close();
3011
3012 // None of the following payments should succeed.
3013 auto failedMptPayments = [this, &env, &btc]() {
3014 env.require(Flags(bob_, asfDepositAuth));
3015
3016 // Capture bob's balances before hand to confirm they don't
3017 // change.
3018 PrettyAmount const bobXrpBalance{env.balance(bob_, XRP)};
3019 PrettyAmount const bobBTCBalance{env.balance(bob_, MPT(btc))};
3020
3021 env(pay(alice_, bob_, btc(50'000)), Ter(tecNO_PERMISSION));
3022 env.close();
3023
3024 // Note that even though alice is paying bob in XRP, the payment
3025 // is still not allowed since the payment passes through an
3026 // offer.
3027 env(pay(alice_, bob_, drops(1)), Sendmax(btc(1'000)), Ter(tecNO_PERMISSION));
3028 env.close();
3029
3030 BEAST_EXPECT(bobXrpBalance == env.balance(bob_, XRP));
3031 BEAST_EXPECT(bobBTCBalance == env.balance(bob_, MPT(btc)));
3032 };
3033
3034 // Test when bob has an XRP balance > base reserve.
3035 failedMptPayments();
3036
3037 // Set bob's XRP balance == base reserve. Also demonstrate that
3038 // bob can make payments while his lsfDepositAuth flag is set.
3039 env(pay(bob_, alice_, btc(25'000)));
3040 env.close();
3041
3042 {
3043 STAmount const bobPaysXRP{env.balance(bob_, XRP) - reserve(env, 1)};
3044 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
3045 env(pay(bob_, alice_, bobPaysXRP), Fee(bobPaysFee));
3046 env.close();
3047 }
3048
3049 // Test when bob's XRP balance == base reserve.
3050 BEAST_EXPECT(env.balance(bob_, XRP) == reserve(env, 0));
3051 BEAST_EXPECT(env.balance(bob_, MPT(btc)) == btc(25'000));
3052 failedMptPayments();
3053
3054 // Test when bob has an XRP balance == 0.
3055 env(noop(bob_), Fee(reserve(env, 0)));
3056 env.close();
3057
3058 BEAST_EXPECT(env.balance(bob_, XRP) == XRP(0));
3059 failedMptPayments();
3060
3061 // Give bob enough XRP for the fee to clear the lsfDepositAuth flag.
3062 env(pay(alice_, bob_, drops(env.current()->fees().base)));
3063
3064 // bob clears the lsfDepositAuth and the next payment succeeds.
3065 env(fclear(bob_, asfDepositAuth));
3066 env.close();
3067
3068 env(pay(alice_, bob_, btc(50'000)));
3069 env.close();
3070
3071 env(pay(alice_, bob_, drops(1)), Sendmax(btc(1'000)));
3072 env.close();
3073 BEAST_EXPECT(ammCarol.expectBalances(btc(101'000), XRPAmount(100), ammCarol.tokens()));
3074 }
3075
3076 void
3078 {
3079 testcase("Individual Lock");
3080
3081 using namespace test::jtx;
3082 Env env(*this, features);
3083
3084 Account const g1{"G1"};
3085 Account const alice{"alice"};
3086 Account const bob{"bob"};
3087
3088 env.fund(XRP(1'000), g1, alice, bob);
3089
3090 MPTTester btc(
3091 {.env = env,
3092 .issuer = g1,
3093 .holders = {alice, bob},
3094 .flags = tfMPTCanLock | kMptDexFlags});
3095
3096 env(pay(g1, bob, btc(10)));
3097 env(pay(g1, alice, btc(205)));
3098 env.close();
3099
3100 AMM const ammAlice(env, alice, XRP(500), btc(105));
3101
3102 env.require(Balance(bob, btc(10)));
3103 env.require(Balance(alice, btc(100)));
3104
3105 // Account with MPT unlocked (proving operations normally work)
3106 // can make Payment
3107 env(pay(alice, bob, btc(1)));
3108
3109 // can receive Payment
3110 env(pay(bob, alice, btc(1)));
3111 env.close();
3112
3113 // Lock MPT for bob
3114 btc.set({.holder = bob, .flags = tfMPTLock});
3115
3116 {
3117 env(offer(bob, btc(5), XRP(25)), Ter(tecLOCKED));
3118 env.close();
3119 BEAST_EXPECT(ammAlice.expectBalances(XRP(500), btc(105), ammAlice.tokens()));
3120 }
3121
3122 {
3123 // can not sell assets
3124 env(offer(bob, XRP(1), btc(5)), Ter(tecUNFUNDED_OFFER));
3125
3126 // different from IOU
3127 // can not receive Payment when locked
3128 env(pay(alice, bob, btc(1)), Ter(tecPATH_DRY));
3129
3130 // can not make Payment when locked
3131 env(pay(bob, alice, btc(1)), Ter(tecPATH_DRY));
3132
3133 env.require(Balance(bob, btc(10)));
3134 }
3135
3136 {
3137 // Unlock
3138 btc.set({.holder = bob, .flags = tfMPTUnlock});
3139 env(offer(bob, XRP(1), btc(5)));
3140 env(pay(bob, alice, btc(1)));
3141 env(pay(alice, bob, btc(1)));
3142 env.close();
3143 }
3144 }
3145
3146 void
3148 {
3149 testcase("Global Lock");
3150
3151 using namespace test::jtx;
3152 Env env(*this, features);
3153
3154 Account const g1{"G1"};
3155 Account a1{"A1"};
3156 Account a2{"A2"};
3157 Account a3{"A3"};
3158 Account a4{"A4"};
3159
3160 env.fund(XRP(12'000), g1);
3161 env.fund(XRP(1'000), a1);
3162 env.fund(XRP(20'000), a2, a3, a4);
3163
3164 MPTTester const eth(
3165 {.env = env,
3166 .issuer = g1,
3167 .holders = {a1, a2, a3, a4},
3168 .flags = tfMPTCanLock | kMptDexFlags});
3169
3170 MPTTester btc(
3171 {.env = env,
3172 .issuer = g1,
3173 .holders = {a1, a2, a3, a4},
3174 .flags = tfMPTCanLock | kMptDexFlags});
3175
3176 env(pay(g1, a1, eth(1'000)));
3177 env(pay(g1, a2, eth(100)));
3178 env(pay(g1, a3, btc(100)));
3179 env(pay(g1, a4, btc(100)));
3180 env.close();
3181
3182 AMM const ammG1(env, g1, XRP(10'000), eth(100));
3183 env(offer(a1, XRP(10'000), eth(100)), Txflags(tfPassive));
3184 env(offer(a2, eth(100), XRP(10'000)), Txflags(tfPassive));
3185 env.close();
3186
3187 {
3188 // Account without Global Lock (proving operations normally
3189 // work)
3190 // visible offers where taker_pays is unlocked issuer
3191 auto offers = getAccountOffers(env, a2)[jss::offers];
3192 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3193 return;
3194
3195 // visible offers where taker_gets is unlocked issuer
3196 offers = getAccountOffers(env, a1)[jss::offers];
3197 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3198 return;
3199 }
3200
3201 {
3202 // Offers/Payments
3203 // assets can be bought on the market
3204 AMM ammA3(env, a3, btc(1), XRP(1));
3205
3206 // assets can be sold on the market
3207 // AMM is bidirectional
3208 env(pay(g1, a2, eth(1)));
3209 env(pay(a2, g1, eth(1)));
3210 env(pay(a2, a1, eth(1)));
3211 env(pay(a1, a2, eth(1)));
3212 ammA3.withdrawAll(std::nullopt);
3213 }
3214
3215 {
3216 // Account with Global Lock
3217 // set Global Lock first
3218 btc.set({.flags = tfMPTLock});
3219
3220 // assets can't be bought on the market
3221 AMM const ammA3(env, a3, btc(1), XRP(1), Ter(tecLOCKED));
3222
3223 // direct issues can be sent
3224 env(pay(g1, a2, btc(1)));
3225 env(pay(a2, g1, btc(1)));
3226 // locked
3227 env(pay(a2, a1, btc(1)), Ter(tecPATH_DRY));
3228 env(pay(a1, a2, btc(1)), Ter(tecPATH_DRY));
3229 }
3230
3231 {
3232 auto offers = getAccountOffers(env, a2)[jss::offers];
3233 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3234 return;
3235
3236 offers = getAccountOffers(env, a1)[jss::offers];
3237 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3238 return;
3239 }
3240 }
3241
3242 void
3244 {
3245 testcase("Offers for Locked MPTs");
3246
3247 using namespace test::jtx;
3248 Env env(*this, features);
3249
3250 Account const g1{"G1"};
3251 Account a2{"A2"};
3252 Account a3{"A3"};
3253 Account a4{"A4"};
3254
3255 env.fund(XRP(2'000), g1, a3, a4);
3256 env.fund(XRP(2'000), a2);
3257 env.close();
3258
3259 MPTTester btc(
3260 {.env = env,
3261 .issuer = g1,
3262 .holders = {a2, a3, a4},
3263 .flags = tfMPTCanLock | kMptDexFlags});
3264
3265 env(pay(g1, a3, btc(2'000)));
3266 env(pay(g1, a4, btc(2'001)));
3267 env.close();
3268
3269 AMM const ammA3(env, a3, XRP(1'000), btc(1'001));
3270
3271 // removal after successful payment
3272 // test: make a payment with partially consuming offer
3273 env(pay(a2, g1, btc(1)), Paths(MPT(btc)), Sendmax(XRP(1)));
3274 env.close();
3275
3276 BEAST_EXPECT(ammA3.expectBalances(XRP(1'001), btc(1'000), ammA3.tokens()));
3277
3278 // test: someone else creates an offer providing liquidity
3279 env(offer(a4, XRP(999), btc(999)));
3280 env.close();
3281 // The offer consumes AMM offer
3282 BEAST_EXPECT(ammA3.expectBalances(XRP(1'000), btc(1'001), ammA3.tokens()));
3283
3284 // test: AMM is Locked
3285 btc.set({.holder = ammA3.ammAccount(), .flags = tfMPTLock});
3286 auto const info = ammA3.ammRpcInfo();
3287 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3288 env.close();
3289
3290 // test: Can make a payment via the new offer
3291 env(pay(a2, g1, btc(1)), Paths(MPT(btc)), Sendmax(XRP(1)));
3292 env.close();
3293 // AMM is not consumed
3294 BEAST_EXPECT(ammA3.expectBalances(XRP(1'000), btc(1'001), ammA3.tokens()));
3295
3296 // removal buy successful OfferCreate
3297 // test: lock the new offer
3298 btc.set({.holder = a4, .flags = tfMPTUnlock});
3299 env.close();
3300
3301 // test: can no longer create a crossing offer
3302 env(offer(a2, btc(999), XRP(999)));
3303 env.close();
3304
3305 // test: offer was removed by offer_create
3306 auto offers = getAccountOffers(env, a4)[jss::offers];
3307 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
3308 return;
3309 }
3310
3311 void
3313 {
3314 testcase("Multisign AMM Transactions");
3315
3316 using namespace jtx;
3317 Env env{*this, features};
3318 Account const bogie{"bogie", KeyType::Secp256k1};
3319 Account const alice{"alice", KeyType::Secp256k1};
3320 Account const becky{"becky", KeyType::Ed25519};
3321 Account const zelda{"zelda", KeyType::Secp256k1};
3322 fund(env, gw_, {alice, becky, zelda}, XRP(20'000));
3323
3324 MPTTester const btc(
3325 {.env = env,
3326 .issuer = gw_,
3327 .holders = {alice, becky, zelda},
3328 .pay = 20'000'000'000,
3329 .flags = kMptDexFlags});
3330
3331 // alice uses a regular key with the master disabled.
3332 Account const alie{"alie", KeyType::Secp256k1};
3333 env(regkey(alice, alie));
3334 env(fset(alice, asfDisableMaster), Sig(alice));
3335
3336 // Attach signers to alice.
3337 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), Sig(alie));
3338 env.close();
3339 static constexpr int kSignerListOwners{2};
3340 env.require(Owners(alice, kSignerListOwners + 0));
3341
3342 Msig const ms{becky, bogie};
3343
3344 // Multisign all AMM transactions
3345 AMM ammAlice(
3346 env,
3347 alice,
3348 XRP(10'000),
3349 btc(10'000),
3350 false,
3351 0,
3352 ammCrtFee(env).drops(),
3353 std::nullopt,
3354 std::nullopt,
3355 ms,
3356 Ter(tesSUCCESS));
3357 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(10'000), ammAlice.tokens()));
3358
3359 ammAlice.deposit(alice, 1'000'000);
3360 BEAST_EXPECT(ammAlice.expectBalances(XRP(11'000), btc(11'000), IOUAmount{11'000'000, 0}));
3361
3362 ammAlice.withdraw(alice, 1'000'000);
3363 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(10'000), ammAlice.tokens()));
3364
3365 ammAlice.vote({}, 1'000);
3366 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3367
3368 env(ammAlice.bid({.account = alice, .bidMin = 100}), ms).close();
3369 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
3370 // 4000 tokens burnt
3371 BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), btc(10'000), IOUAmount{9'996'000, 0}));
3372 }
3373
3374 void
3376 {
3377 testcase("To Strand");
3378
3379 using namespace jtx;
3380
3381 // cannot have more than one offer with the same output issue
3382 {
3383 auto test = [&](auto&& issue1, auto&& issue2) {
3384 Env env(*this);
3385 env.fund(XRP(30'000), alice_, bob_, carol_, gw_);
3386 env.close();
3387 auto const eth = issue1(
3388 {.env = env,
3389 .token = "ETH",
3390 .issuer = gw_,
3391 .holders = {alice_, bob_, carol_},
3392 .limit = 1'000'000});
3393 auto const btc = issue2(
3394 {.env = env,
3395 .token = "BTC",
3396 .issuer = gw_,
3397 .holders = {alice_, bob_, carol_},
3398 .limit = 1'000'000});
3399 env(pay(gw_, alice_, btc(50000)));
3400 env(pay(gw_, bob_, btc(50000)));
3401 env(pay(gw_, carol_, btc(50000)));
3402 env(pay(gw_, alice_, eth(50000)));
3403 env(pay(gw_, bob_, eth(50000)));
3404 env(pay(gw_, carol_, eth(50000)));
3405 env.close();
3406 AMM const bobXrpBtc(env, bob_, XRP(1'000), btc(1'000));
3407 AMM const bobBtcEth(env, bob_, btc(1'000), eth(1'000));
3408
3409 // payment path: XRP -> XRP/BTC -> BTC/ETH -> ETH/BTC
3410 env(pay(alice_, carol_, btc(100)),
3411 Path(~btc, ~eth, ~btc),
3412 Sendmax(XRP(200)),
3413 Txflags(tfNoRippleDirect),
3415 };
3417 }
3418 }
3419
3420 void
3422 {
3423 using namespace jtx;
3424 testcase("RIPD1373");
3425
3426 {
3427 Env env(*this, features);
3428 fund(env, gw_, {alice_, bob_}, XRP(10'000));
3429
3430 MPTTester btc(
3431 {.env = env,
3432 .issuer = bob_,
3433 .holders = {alice_, gw_},
3434 .pay = 100'000'000,
3435 .flags = kMptDexFlags});
3436
3437 MPTTester eth(
3438 {.env = env,
3439 .issuer = bob_,
3440 .holders = {alice_, gw_},
3441 .pay = 100'000'000,
3442 .flags = kMptDexFlags});
3443
3444 AMM const ammXrpBtc(env, bob_, XRP(100), btc(100'000));
3445 env(offer(gw_, XRP(100), btc(100'000)), Txflags(tfPassive));
3446
3447 AMM const ammBtcEth(env, bob_, btc(100'000), eth(100'000));
3448 env(offer(gw_, btc(100'000), eth(100'000)), Txflags(tfPassive));
3449
3450 TestPath const p = [&] {
3451 TestPath result;
3452 result.pushBack(allPathElements(gw_, MPT(btc)));
3453 result.pushBack(cpe(eth.issuanceID()));
3454 return result;
3455 }();
3456
3457 PathSet const paths(p);
3458
3459 env(pay(alice_, alice_, eth(1'000)),
3460 Json(paths.json()),
3461 Sendmax(XRP(10)),
3462 Txflags(tfNoRippleDirect | tfPartialPayment),
3463 Ter(temBAD_PATH));
3464 }
3465
3466 {
3467 Env env(*this, features);
3468
3469 fund(env, gw_, {alice_, bob_, carol_}, XRP(10'000));
3470
3471 MPTTester const btc(
3472 {.env = env,
3473 .issuer = gw_,
3474 .holders = {alice_, bob_, carol_},
3475 .pay = 100'000,
3476 .flags = kMptDexFlags});
3477
3478 AMM const ammBob(env, bob_, XRP(100), btc(100));
3479
3480 // payment path: XRP -> XRP/BTC -> BTC/XRP
3481 env(pay(alice_, carol_, XRP(100)),
3482 Path(~MPT(btc), ~XRP),
3483 Txflags(tfNoRippleDirect),
3485 }
3486
3487 {
3488 Env env(*this, features);
3489
3490 fund(env, gw_, {alice_, bob_, carol_}, XRP(10'000));
3491
3492 MPTTester const btc(
3493 {.env = env,
3494 .issuer = gw_,
3495 .holders = {alice_, bob_, carol_},
3496 .pay = 100'000,
3497 .flags = kMptDexFlags});
3498
3499 AMM const ammBob(env, bob_, XRP(100), btc(100));
3500
3501 // payment path: XRP -> XRP/BTC -> BTC/XRP
3502 env(pay(alice_, carol_, XRP(100)),
3503 Path(~MPT(btc), ~XRP),
3504 Sendmax(XRP(200)),
3505 Txflags(tfNoRippleDirect),
3507 }
3508 }
3509
3510 void
3512 {
3513 testcase("test loop");
3514 using namespace jtx;
3515
3516 {
3517 Env env(*this, features);
3518
3519 env.fund(XRP(10'000), alice_, bob_, carol_, gw_);
3520
3521 MPTTester const btc(
3522 {.env = env,
3523 .issuer = gw_,
3524 .holders = {alice_, bob_, carol_},
3525 .flags = kMptDexFlags});
3526
3527 env(pay(gw_, bob_, btc(100'000'000)));
3528 env(pay(gw_, alice_, btc(100'000'000)));
3529 env.close();
3530
3531 AMM const ammBob(env, bob_, XRP(100), btc(100'000'000));
3532
3533 // payment path: BTC -> BTC/XRP -> XRP/BTC
3534 env(pay(alice_, carol_, btc(100'000'000)),
3535 Sendmax(btc(100'000'000)),
3536 Path(~XRP, ~MPT(btc)),
3537 Txflags(tfNoRippleDirect),
3539 }
3540
3541 {
3542 auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) {
3543 Env env(*this, features);
3544
3545 env.fund(XRP(10'000), alice_, bob_, carol_, gw_);
3546 env.close();
3547
3548 auto const btc = issue1(
3549 {.env = env, .token = "BTC", .issuer = gw_, .holders = {alice_, bob_, carol_}});
3550 auto const eth = issue2(
3551 {.env = env, .token = "ETH", .issuer = gw_, .holders = {alice_, bob_, carol_}});
3552 auto const cny = issue3(
3553 {.env = env, .token = "CNY", .issuer = gw_, .holders = {alice_, bob_, carol_}});
3554
3555 env(pay(gw_, bob_, btc(200)));
3556 env(pay(gw_, bob_, eth(200)));
3557 env(pay(gw_, bob_, cny(100)));
3558 env.close();
3559
3560 AMM const ammBobXrpBtc(env, bob_, XRP(100), btc(100));
3561 AMM const ammBobBtcEth(env, bob_, btc(100), eth(100));
3562 AMM const ammBobEthCny(env, bob_, eth(100), cny(100));
3563
3564 // payment path: XRP->XRP/BTC->BTC/ETH->BTC/CNY
3565 env(pay(alice_, carol_, cny(100)),
3566 Sendmax(XRP(100)),
3567 Path(~btc, ~eth, ~btc, ~cny),
3568 Txflags(tfNoRippleDirect),
3570 };
3572 }
3573 }
3574
3575 void
3577 {
3580 receiveMax();
3581 pathFind01();
3582 pathFind02();
3583 pathFind06();
3584 }
3585
3586 void
3588 {
3589 using namespace jtx;
3590 FeatureBitset const all{testableAmendments()};
3591
3592 testFalseDry(all);
3593 testBookStep(all);
3597 }
3598
3599 void
3601 {
3602 using namespace jtx;
3603 FeatureBitset const all{testableAmendments()};
3604 testStepLimit(all);
3605 }
3606
3607 void
3609 {
3610 using namespace jtx;
3611 FeatureBitset const all{testableAmendments()};
3613 }
3614
3615 void
3617 {
3618 auto const supported{jtx::testableAmendments()};
3619 testPayment(supported);
3620 testPayMPT();
3621 }
3622
3623 void
3625 {
3626 using namespace test::jtx;
3627 auto const sa = testableAmendments();
3629 testGlobalLock(sa);
3631 }
3632
3633 void
3635 {
3636 using namespace jtx;
3637 auto const all = testableAmendments();
3638
3639 testTxMultisign(all);
3640 }
3641
3642 void
3644 {
3645 using namespace jtx;
3646 auto const all = testableAmendments();
3647
3648 testToStrand(all);
3649 testRIPD1373(all);
3650 testLoop(all);
3651 }
3652
3653 void
3654 run() override
3655 {
3656 testOffers();
3657 testPaths();
3658 testFlow();
3662 testLock();
3663 testMultisign();
3664 testPayStrand();
3665 }
3666};
3667
3668BEAST_DEFINE_TESTSUITE_PRIO(AMMExtendedMPT, app, xrpl, 1);
3669
3670} // namespace xrpl::test
A generic endpoint for log messages.
Definition Journal.h:38
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Represents a JSON value.
Definition json_value.h:130
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:24
beast::Journal journal(std::string const &name)
Definition Log.cpp:137
bool modify(modify_type const &f)
Modify the open ledger.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:45
A wrapper which makes credits unavailable to balances.
void pushBack(STPath const &e)
Definition STPathSet.h:540
bool empty() const
Definition STPathSet.h:534
Discardable, editable view to a ledger.
Definition Sandbox.h:15
void apply(RawView &to)
Definition Sandbox.h:35
virtual OpenLedger & getOpenLedger()=0
virtual Logs & getLogs()=0
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
json::Value json() const
Definition PathSet.h:170
TestPath & pushBack(Issue const &iss)
Definition PathSet.h:108
jtx::Account const alice_
Definition AMMTest.h:74
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> const &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={testableAmendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition AMMTest.cpp:120
jtx::Account const gw_
Definition AMMTest.h:72
static FeatureBitset testableAmendments()
Definition AMMTest.h:86
jtx::Account const carol_
Definition AMMTest.h:73
jtx::Account const bob_
Definition AMMTest.h:75
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
Definition AMMTest.cpp:198
static XRPAmount ammCrtFee(jtx::Env &env)
Definition AMMTest.cpp:204
Convenience class to test AMM functionality.
bool expectTradingFee(std::uint16_t fee) const
Definition AMM.cpp:340
json::Value bid(BidArg const &arg)
Definition AMM.cpp:737
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< Ter > const &ter=std::nullopt)
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition AMM.cpp:307
IOUAmount tokens() const
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)
Definition AMM.cpp:466
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)
Definition AMM.cpp:607
AccountID const & ammAccount() const
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< Asset, Asset > > const &assets=std::nullopt, std::optional< Ter > const &ter=std::nullopt)
Definition AMM.cpp:711
json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Asset > const &asset1=std::nullopt, std::optional< Asset > const &asset2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::kApiInvalidVersion) const
Send amm_info RPC command.
Definition AMM.cpp:183
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition AMM.cpp:269
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
Sets the DeliverMin on a JTx.
Definition delivermin.h:13
A transaction testing environment.
Definition Env.h:143
Application & app()
Definition Env.h:280
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:296
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:275
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:864
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:201
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:327
void memoize(Account const &account)
Associate AccountID with account.
Definition Env.cpp:174
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:605
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:353
Set the fee on a JTx.
Definition fee.h:15
Match set account flags.
Definition flags.h:109
Inject raw JSON.
Definition jtx_json.h:11
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:385
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:368
Converts to MPT Issue or STAmount.
Set a multisignature on a JTx.
Definition multisign.h:39
Match the number of items in the account's owner directory.
Definition owners.h:52
Add a path.
Definition paths.h:39
Set Paths, SendMax on a JTx.
Definition paths.h:16
Check a set of conditions.
Definition require.h:45
Sets the SendMax on a JTx.
Definition sendmax.h:13
Set the regular signature on a JTx.
Definition sig.h:13
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:13
Set the flags on a JTx.
Definition txflags.h:9
@ Array
array value (ordered list)
Definition json_value.h:25
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:264
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
void nOffers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
static NoneT const kNone
Definition tags.h:9
auto const kMptDexFlags
Definition mpt.h:25
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
std::vector< STAmount > fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition AMMTest.cpp:34
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
json::Value regkey(Account const &account, DisabledT)
Disable the regular key.
Definition regkey.cpp:13
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
STPathElement allPathElements(AccountID const &a, Asset const &asset)
void testHelper2TokensMix(TTester &&tester)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value noop(Account const &account)
The null transaction.
Definition noop.h:9
bool same(STPathSet const &st1, Args const &... args)
XRPAmount txFee(Env const &env, std::uint16_t n)
std::tuple< STPathSet, STAmount, STAmount > findPaths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax, std::optional< PathAsset > const &srcAsset, std::optional< AccountID > const &srcIssuer, std::optional< uint256 > const &domain)
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:102
FeatureBitset testableAmendments()
Definition Env.h:76
void testHelper3TokensMix(TTester &&tester)
STPathElement cpe(PathAsset const &pa)
std::tuple< STPathSet, STAmount, STAmount > findPathsByElement(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax, std::optional< STPathElement > const &srcElement, std::optional< AccountID > const &srcIssuer, std::optional< uint256 > const &domain)
STPathElement ipe(Asset const &asset)
static auto gAmmmpt
Definition AMMTest.h:33
bool equal(STAmount const &sa1, STAmount const &sa2)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
json::Value ledgerEntryRoot(Env &env, Account const &acct)
OwnerCount< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition owners.h:70
STPath stpath(Args const &... args)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
Definition multisign.cpp:31
bool checkArraySize(json::Value const &val, unsigned int size)
json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:48
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2)
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
Definition PathSet.h:48
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
TAmounts< STAmount, STAmount > Amounts
Definition Quality.h:64
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:58
TER offerDelete(ApplyView &view, SLE::ref sle, beast::Journal j)
Delete an offer.
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:81
@ TapNone
Definition ApplyView.h:13
@ temBAD_PATH
Definition TER.h:82
@ temBAD_SEND_XRP_PATHS
Definition TER.h:89
@ temBAD_SEND_XRP_MAX
Definition TER.h:86
@ temBAD_PATH_LOOP
Definition TER.h:83
@ temBAD_AMOUNT
Definition TER.h:75
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecLOCKED
Definition TER.h:356
@ tecPATH_PARTIAL
Definition TER.h:280
@ tecPATH_DRY
Definition TER.h:292
@ tecNO_AUTH
Definition TER.h:298
@ tecUNFUNDED_OFFER
Definition TER.h:282
@ tecKILLED
Definition TER.h:314
@ tecNO_PERMISSION
Definition TER.h:303
@ tesSUCCESS
Definition TER.h:240
Tests of AMM MPT that use offers.
void testOfferCrossWithXRP(FeatureBitset features)
void testSellFlagBasic(FeatureBitset features)
void testSellWithFillOrKill(FeatureBitset features)
void testBridgedCross(FeatureBitset features)
void testCrossCurrencyEndXRP(FeatureBitset features)
void testCurrencyConversionEntire(FeatureBitset features)
void testTxMultisign(FeatureBitset features)
void testDirectToDirectPath(FeatureBitset features)
void testOffersWhenLocked(FeatureBitset features)
void testFalseDry(FeatureBitset features)
void testCrossCurrencyBridged(FeatureBitset features)
void testOfferFeesConsumeFunds(FeatureBitset features)
void testRequireAuth(FeatureBitset features)
void testCurrencyConversionInParts(FeatureBitset features)
void testOfferCreateThenCross(FeatureBitset features)
void run() override
Runs the suite.
void testStepLimit(FeatureBitset features)
void testSellFlagExceedLimit(FeatureBitset features)
void testFillModes(FeatureBitset features)
void testPayment(FeatureBitset features)
void testSelfIssueOffer(FeatureBitset features)
void testOfferCrossWithLimitOverride(FeatureBitset features)
void testTransferRateOffer(FeatureBitset features)
void testRmFundedOffer(FeatureBitset features)
void testLoop(FeatureBitset features)
void testConvertAllOfAnAsset(FeatureBitset features)
void testCrossCurrencyStartXRP(FeatureBitset features)
void testToStrand(FeatureBitset features)
void testGlobalLock(FeatureBitset features)
void testTransferRateNoOwnerFee(FeatureBitset features)
void testMissingAuth(FeatureBitset features)
void testGatewayCrossCurrency(FeatureBitset features)
void testRIPD1373(FeatureBitset features)
void testIndividualLock(FeatureBitset features)
void testBookStep(FeatureBitset features)
Represents an XRP, IOU, or MPT quantity This customizes the string conversion and supports XRP conver...
T tie(T... args)