rippled
Loading...
Searching...
No Matches
Offer_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/PathSet.h>
3#include <test/jtx/WSClient.h>
4
5#include <xrpl/protocol/Feature.h>
6#include <xrpl/protocol/Quality.h>
7#include <xrpl/protocol/jss.h>
8
9namespace xrpl {
10namespace test {
11
13{
16 {
17 return env.current()->fees().accountReserve(count);
18 }
19
22 {
23 return env.current()
24 ->header()
25 .parentCloseTime.time_since_epoch()
26 .count();
27 }
28
29 static auto
31 jtx::Env& env,
32 jtx::Account const& acct,
33 std::uint32_t offer_seq)
34 {
35 Json::Value jvParams;
36 jvParams[jss::offer][jss::account] = acct.human();
37 jvParams[jss::offer][jss::seq] = offer_seq;
38 return env.rpc(
39 "json", "ledger_entry", to_string(jvParams))[jss::result];
40 }
41
42 static auto
44 jtx::Env& env,
45 Issue const& taker_pays,
46 Issue const& taker_gets)
47 {
48 Json::Value jvbp;
49 jvbp[jss::ledger_index] = "current";
50 jvbp[jss::taker_pays][jss::currency] = to_string(taker_pays.currency);
51 jvbp[jss::taker_pays][jss::issuer] = to_string(taker_pays.account);
52 jvbp[jss::taker_gets][jss::currency] = to_string(taker_gets.currency);
53 jvbp[jss::taker_gets][jss::issuer] = to_string(taker_gets.account);
54 return env.rpc("json", "book_offers", to_string(jvbp))[jss::result];
55 }
56
57public:
58 void
60 {
61 testcase("Incorrect Removal of Funded Offers");
62
63 // We need at least two paths. One at good quality and one at bad
64 // quality. The bad quality path needs two offer books in a row.
65 // Each offer book should have two offers at the same quality, the
66 // offers should be completely consumed, and the payment should
67 // should require both offers to be satisfied. The first offer must
68 // be "taker gets" XRP. Old, broken would remove the first
69 // "taker gets" xrp offer, even though the offer is still funded and
70 // not used for the payment.
71
72 using namespace jtx;
73 Env env{*this, features};
74
75 auto const gw = Account{"gateway"};
76 auto const USD = gw["USD"];
77 auto const BTC = gw["BTC"];
78 Account const alice{"alice"};
79 Account const bob{"bob"};
80 Account const carol{"carol"};
81
82 env.fund(XRP(10000), alice, bob, carol, gw);
83 env.close();
84 env.trust(USD(1000), alice, bob, carol);
85 env.trust(BTC(1000), alice, bob, carol);
86
87 env(pay(gw, alice, BTC(1000)));
88
89 env(pay(gw, carol, USD(1000)));
90 env(pay(gw, carol, BTC(1000)));
91
92 // Must be two offers at the same quality
93 // "taker gets" must be XRP
94 // (Different amounts so I can distinguish the offers)
95 env(offer(carol, BTC(49), XRP(49)));
96 env(offer(carol, BTC(51), XRP(51)));
97
98 // Offers for the poor quality path
99 // Must be two offers at the same quality
100 env(offer(carol, XRP(50), USD(50)));
101 env(offer(carol, XRP(50), USD(50)));
102
103 // Offers for the good quality path
104 env(offer(carol, BTC(1), USD(100)));
105
106 PathSet paths(Path(XRP, USD), Path(USD));
107
108 env(pay(alice, bob, USD(100)),
109 json(paths.json()),
110 sendmax(BTC(1000)),
112
113 env.require(balance(bob, USD(100)));
114 BEAST_EXPECT(
115 !isOffer(env, carol, BTC(1), USD(100)) &&
116 isOffer(env, carol, BTC(49), XRP(49)));
117 }
118
119 void
121 {
122 testcase("Removing Canceled Offers");
123
124 using namespace jtx;
125 Env env{*this, features};
126
127 auto const gw = Account{"gateway"};
128 auto const alice = Account{"alice"};
129 auto const USD = gw["USD"];
130
131 env.fund(XRP(10000), alice, gw);
132 env.close();
133 env.trust(USD(100), alice);
134 env.close();
135
136 env(pay(gw, alice, USD(50)));
137 env.close();
138
139 auto const offer1Seq = env.seq(alice);
140
141 env(offer(alice, XRP(500), USD(100)), require(offers(alice, 1)));
142 env.close();
143
144 BEAST_EXPECT(isOffer(env, alice, XRP(500), USD(100)));
145
146 // cancel the offer above and replace it with a new offer
147 auto const offer2Seq = env.seq(alice);
148
149 env(offer(alice, XRP(300), USD(100)),
150 json(jss::OfferSequence, offer1Seq),
151 require(offers(alice, 1)));
152 env.close();
153
154 BEAST_EXPECT(
155 isOffer(env, alice, XRP(300), USD(100)) &&
156 !isOffer(env, alice, XRP(500), USD(100)));
157
158 // Test canceling non-existent offer.
159 // auto const offer3Seq = env.seq (alice);
160
161 env(offer(alice, XRP(400), USD(200)),
162 json(jss::OfferSequence, offer1Seq),
163 require(offers(alice, 2)));
164 env.close();
165
166 BEAST_EXPECT(
167 isOffer(env, alice, XRP(300), USD(100)) &&
168 isOffer(env, alice, XRP(400), USD(200)));
169
170 // Test cancellation now with OfferCancel tx
171 auto const offer4Seq = env.seq(alice);
172 env(offer(alice, XRP(222), USD(111)), require(offers(alice, 3)));
173 env.close();
174
175 BEAST_EXPECT(isOffer(env, alice, XRP(222), USD(111)));
176 env(offer_cancel(alice, offer4Seq));
177 env.close();
178 BEAST_EXPECT(env.seq(alice) == offer4Seq + 2);
179
180 BEAST_EXPECT(!isOffer(env, alice, XRP(222), USD(111)));
181
182 // Create an offer that both fails with a tecEXPIRED code and removes
183 // an offer. Show that the attempt to remove the offer fails.
184 env.require(offers(alice, 2));
185
186 env(offer(alice, XRP(5), USD(2)),
187 json(sfExpiration.fieldName, lastClose(env)),
188 json(jss::OfferSequence, offer2Seq),
189 ter(tecEXPIRED));
190 env.close();
191
192 env.require(offers(alice, 2));
193 BEAST_EXPECT(isOffer(env, alice, XRP(300), USD(100))); // offer2
194 BEAST_EXPECT(!isOffer(env, alice, XRP(5), USD(2))); // expired
195 }
196
197 void
199 {
200 testcase("Tiny payments");
201
202 // Regression test for tiny payments
203 using namespace jtx;
204 using namespace std::chrono_literals;
205 auto const alice = Account{"alice"};
206 auto const bob = Account{"bob"};
207 auto const carol = Account{"carol"};
208 auto const gw = Account{"gw"};
209
210 auto const USD = gw["USD"];
211 auto const EUR = gw["EUR"];
212
213 Env env{*this, features};
214
215 env.fund(XRP(10000), alice, bob, carol, gw);
216 env.close();
217 env.trust(USD(1000), alice, bob, carol);
218 env.trust(EUR(1000), alice, bob, carol);
219 env(pay(gw, alice, USD(100)));
220 env(pay(gw, carol, EUR(100)));
221
222 // Create more offers than the loop max count in DeliverNodeReverse
223 // Note: the DeliverNodeReverse code has been removed; however since
224 // this is a regression test the original test is being left as-is for
225 // now.
226 for (int i = 0; i < 101; ++i)
227 env(offer(carol, USD(1), EUR(2)));
228
229 env(pay(alice, bob, EUR(epsilon)), path(~EUR), sendmax(USD(100)));
230 }
231
232 void
234 {
235 testcase("XRP Tiny payments");
236
237 // Regression test for tiny xrp payments
238 // In some cases, when the payment code calculates
239 // the amount of xrp needed as input to an xrp->iou offer
240 // it would incorrectly round the amount to zero (even when
241 // round-up was set to true).
242 // The bug would cause funded offers to be incorrectly removed
243 // because the code thought they were unfunded.
244 // The conditions to trigger the bug are:
245 // 1) When we calculate the amount of input xrp needed for an offer
246 // from xrp->iou, the amount is less than 1 drop (after rounding
247 // up the float representation).
248 // 2) There is another offer in the same book with a quality
249 // sufficiently bad that when calculating the input amount
250 // needed the amount is not set to zero.
251
252 using namespace jtx;
253 using namespace std::chrono_literals;
254 auto const alice = Account{"alice"};
255 auto const bob = Account{"bob"};
256 auto const carol = Account{"carol"};
257 auto const dan = Account{"dan"};
258 auto const erin = Account{"erin"};
259 auto const gw = Account{"gw"};
260
261 auto const USD = gw["USD"];
262 Env env{*this, features};
263
264 env.fund(XRP(10000), alice, bob, carol, dan, erin, gw);
265 env.close();
266 env.trust(USD(1000), alice, bob, carol, dan, erin);
267 env.close();
268 env(pay(gw, carol, USD(0.99999)));
269 env(pay(gw, dan, USD(1)));
270 env(pay(gw, erin, USD(1)));
271 env.close();
272
273 // Carol doesn't quite have enough funds for this offer
274 // The amount left after this offer is taken will cause
275 // STAmount to incorrectly round to zero when the next offer
276 // (at a good quality) is considered. (when the now removed
277 // stAmountCalcSwitchover2 patch was inactive)
278 env(offer(carol, drops(1), USD(0.99999)));
279 // Offer at a quality poor enough so when the input xrp is
280 // calculated in the reverse pass, the amount is not zero.
281 env(offer(dan, XRP(100), USD(1)));
282
283 env.close();
284 // This is the funded offer that will be incorrectly removed.
285 // It is considered after the offer from carol, which leaves a
286 // tiny amount left to pay. When calculating the amount of xrp
287 // needed for this offer, it will incorrectly compute zero in both
288 // the forward and reverse passes (when the now removed
289 // stAmountCalcSwitchover2 was inactive.)
290 env(offer(erin, drops(2), USD(1)));
291
292 env(pay(alice, bob, USD(1)),
293 path(~USD),
294 sendmax(XRP(102)),
296
297 env.require(offers(carol, 0), offers(dan, 1));
298
299 // offer was correctly consumed. There is still some
300 // liquidity left on that offer.
301 env.require(balance(erin, USD(0.99999)), offers(erin, 1));
302 }
303
304 void
306 {
307 testcase("Rm small increased q offers XRP");
308
309 // Carol places an offer, but cannot fully fund the offer. When her
310 // funding is taken into account, the offer's quality drops below its
311 // initial quality and has an input amount of 1 drop. This is removed as
312 // an offer that may block offer books.
313
314 using namespace jtx;
315 using namespace std::chrono_literals;
316 auto const alice = Account{"alice"};
317 auto const bob = Account{"bob"};
318 auto const carol = Account{"carol"};
319 auto const gw = Account{"gw"};
320
321 auto const USD = gw["USD"];
322
323 // Test offer crossing
324 for (auto crossBothOffers : {false, true})
325 {
326 Env env{*this, features};
327
328 env.fund(XRP(10000), alice, bob, carol, gw);
329 env.close();
330 env.trust(USD(1000), alice, bob, carol);
331 // underfund carol's offer
332 auto initialCarolUSD = USD(0.499);
333 env(pay(gw, carol, initialCarolUSD));
334 env(pay(gw, bob, USD(100)));
335 env.close();
336 // This offer is underfunded
337 env(offer(carol, drops(1), USD(1)));
338 env.close();
339 // offer at a lower quality
340 env(offer(bob, drops(2), USD(1), tfPassive));
341 env.close();
342 env.require(offers(bob, 1), offers(carol, 1));
343
344 // alice places an offer that crosses carol's; depending on
345 // "crossBothOffers" it may cross bob's as well
346 auto aliceTakerGets = crossBothOffers ? drops(2) : drops(1);
347 env(offer(alice, USD(1), aliceTakerGets));
348 env.close();
349
350 env.require(
351 offers(carol, 0),
352 balance(
353 carol,
354 initialCarolUSD)); // offer is removed but not taken
355 if (crossBothOffers)
356 {
357 env.require(
358 offers(alice, 0),
359 balance(alice, USD(1))); // alice's offer is crossed
360 }
361 else
362 {
363 env.require(
364 offers(alice, 1),
365 balance(alice, USD(0))); // alice's offer is not crossed
366 }
367 }
368
369 // Test payments
370 for (auto partialPayment : {false, true})
371 {
372 Env env{*this, features};
373
374 env.fund(XRP(10000), alice, bob, carol, gw);
375 env.close();
376 env.trust(USD(1000), alice, bob, carol);
377 env.close();
378 auto const initialCarolUSD = USD(0.999);
379 env(pay(gw, carol, initialCarolUSD));
380 env.close();
381 env(pay(gw, bob, USD(100)));
382 env.close();
383 env(offer(carol, drops(1), USD(1)));
384 env.close();
385 env(offer(bob, drops(2), USD(2), tfPassive));
386 env.close();
387 env.require(offers(bob, 1), offers(carol, 1));
388
389 std::uint32_t const flags = partialPayment
392
393 TER const expectedTer =
394 partialPayment ? TER{tesSUCCESS} : TER{tecPATH_PARTIAL};
395
396 env(pay(alice, bob, USD(5)),
397 path(~USD),
398 sendmax(XRP(1)),
399 txflags(flags),
400 ter(expectedTer));
401 env.close();
402
403 if (expectedTer == tesSUCCESS)
404 {
405 env.require(offers(carol, 0));
406 env.require(balance(
407 carol,
408 initialCarolUSD)); // offer is removed but not taken
409 }
410 else
411 {
412 // TODO: Offers are not removed when payments fail
413 // If that is addressed, the test should show that carol's
414 // offer is removed but not taken, as in the other branch of
415 // this if statement
416 }
417 }
418 }
419
420 void
422 {
423 testcase("Rm small increased q offers IOU");
424
425 // Carol places an offer, but cannot fully fund the offer. When her
426 // funding is taken into account, the offer's quality drops below its
427 // initial quality and has an input amount of 1 drop. This is removed as
428 // an offer that may block offer books.
429
430 using namespace jtx;
431 using namespace std::chrono_literals;
432 auto const alice = Account{"alice"};
433 auto const bob = Account{"bob"};
434 auto const carol = Account{"carol"};
435 auto const gw = Account{"gw"};
436
437 auto const USD = gw["USD"];
438 auto const EUR = gw["EUR"];
439
440 auto tinyAmount = [&](IOU const& iou) -> PrettyAmount {
441 STAmount amt(
442 iou.issue(),
443 /*mantissa*/ 1,
444 /*exponent*/ -81);
445 return PrettyAmount(amt, iou.account.name());
446 };
447
448 // Test offer crossing
449 for (auto crossBothOffers : {false, true})
450 {
451 Env env{*this, features};
452
453 env.fund(XRP(10000), alice, bob, carol, gw);
454 env.close();
455 env.trust(USD(1000), alice, bob, carol);
456 env.trust(EUR(1000), alice, bob, carol);
457 // underfund carol's offer
458 auto initialCarolUSD = tinyAmount(USD);
459 env(pay(gw, carol, initialCarolUSD));
460 env(pay(gw, bob, USD(100)));
461 env(pay(gw, alice, EUR(100)));
462 env.close();
463 // This offer is underfunded
464 env(offer(carol, EUR(1), USD(10)));
465 env.close();
466 // offer at a lower quality
467 env(offer(bob, EUR(1), USD(5), tfPassive));
468 env.close();
469 env.require(offers(bob, 1), offers(carol, 1));
470
471 // alice places an offer that crosses carol's; depending on
472 // "crossBothOffers" it may cross bob's as well
473 // Whatever
474 auto aliceTakerGets = crossBothOffers ? EUR(0.2) : EUR(0.1);
475 env(offer(alice, USD(1), aliceTakerGets));
476 env.close();
477
478 env.require(
479 offers(carol, 0),
480 balance(
481 carol,
482 initialCarolUSD)); // offer is removed but not taken
483 if (crossBothOffers)
484 {
485 env.require(
486 offers(alice, 0),
487 balance(alice, USD(1))); // alice's offer is crossed
488 }
489 else
490 {
491 env.require(
492 offers(alice, 1),
493 balance(alice, USD(0))); // alice's offer is not crossed
494 }
495 }
496
497 // Test payments
498 for (auto partialPayment : {false, true})
499 {
500 Env env{*this, features};
501
502 env.fund(XRP(10000), alice, bob, carol, gw);
503 env.close();
504 env.trust(USD(1000), alice, bob, carol);
505 env.trust(EUR(1000), alice, bob, carol);
506 env.close();
507 // underfund carol's offer
508 auto const initialCarolUSD = tinyAmount(USD);
509 env(pay(gw, carol, initialCarolUSD));
510 env(pay(gw, bob, USD(100)));
511 env(pay(gw, alice, EUR(100)));
512 env.close();
513 // This offer is underfunded
514 env(offer(carol, EUR(1), USD(2)));
515 env.close();
516 env(offer(bob, EUR(2), USD(4), tfPassive));
517 env.close();
518 env.require(offers(bob, 1), offers(carol, 1));
519
520 std::uint32_t const flags = partialPayment
523
524 TER const expectedTer =
525 partialPayment ? TER{tesSUCCESS} : TER{tecPATH_PARTIAL};
526
527 env(pay(alice, bob, USD(5)),
528 path(~USD),
529 sendmax(EUR(10)),
530 txflags(flags),
531 ter(expectedTer));
532 env.close();
533
534 if (expectedTer == tesSUCCESS)
535 {
536 env.require(offers(carol, 0));
537 env.require(balance(
538 carol,
539 initialCarolUSD)); // offer is removed but not taken
540 }
541 else
542 {
543 // TODO: Offers are not removed when payments fail
544 // If that is addressed, the test should show that carol's
545 // offer is removed but not taken, as in the other branch of
546 // this if statement
547 }
548 }
549 }
550
551 void
553 {
554 testcase("Enforce No Ripple");
555
556 using namespace jtx;
557
558 auto const gw = Account{"gateway"};
559 auto const USD = gw["USD"];
560 auto const BTC = gw["BTC"];
561 auto const EUR = gw["EUR"];
562 Account const alice{"alice"};
563 Account const bob{"bob"};
564 Account const carol{"carol"};
565 Account const dan{"dan"};
566
567 {
568 // No ripple with an implied account step after an offer
569 Env env{*this, features};
570
571 auto const gw1 = Account{"gw1"};
572 auto const USD1 = gw1["USD"];
573 auto const gw2 = Account{"gw2"};
574 auto const USD2 = gw2["USD"];
575
576 env.fund(XRP(10000), alice, noripple(bob), carol, dan, gw1, gw2);
577 env.close();
578 env.trust(USD1(1000), alice, carol, dan);
579 env(trust(bob, USD1(1000), tfSetNoRipple));
580 env.trust(USD2(1000), alice, carol, dan);
581 env(trust(bob, USD2(1000), tfSetNoRipple));
582
583 env(pay(gw1, dan, USD1(50)));
584 env(pay(gw1, bob, USD1(50)));
585 env(pay(gw2, bob, USD2(50)));
586
587 env(offer(dan, XRP(50), USD1(50)));
588
589 env(pay(alice, carol, USD2(50)),
590 path(~USD1, bob),
591 sendmax(XRP(50)),
594 }
595 {
596 // Make sure payment works with default flags
597 Env env{*this, features};
598
599 auto const gw1 = Account{"gw1"};
600 auto const USD1 = gw1["USD"];
601 auto const gw2 = Account{"gw2"};
602 auto const USD2 = gw2["USD"];
603
604 env.fund(XRP(10000), alice, bob, carol, dan, gw1, gw2);
605 env.close();
606 env.trust(USD1(1000), alice, bob, carol, dan);
607 env.trust(USD2(1000), alice, bob, carol, dan);
608
609 env(pay(gw1, dan, USD1(50)));
610 env(pay(gw1, bob, USD1(50)));
611 env(pay(gw2, bob, USD2(50)));
612
613 env(offer(dan, XRP(50), USD1(50)));
614
615 env(pay(alice, carol, USD2(50)),
616 path(~USD1, bob),
617 sendmax(XRP(50)),
619
620 env.require(balance(alice, xrpMinusFee(env, 10000 - 50)));
621 env.require(balance(bob, USD1(100)));
622 env.require(balance(bob, USD2(0)));
623 env.require(balance(carol, USD2(50)));
624 }
625 }
626
627 void
629 {
630 testcase("Insufficient Reserve");
631
632 // If an account places an offer and its balance
633 // *before* the transaction began isn't high enough
634 // to meet the reserve *after* the transaction runs,
635 // then no offer should go on the books but if the
636 // offer partially or fully crossed the tx succeeds.
637
638 using namespace jtx;
639
640 auto const gw = Account{"gateway"};
641 auto const alice = Account{"alice"};
642 auto const bob = Account{"bob"};
643 auto const carol = Account{"carol"};
644 auto const USD = gw["USD"];
645
646 auto const usdOffer = USD(1000);
647 auto const xrpOffer = XRP(1000);
648
649 // No crossing:
650 {
651 Env env{*this, features};
652
653 env.fund(XRP(1000000), gw);
654
655 auto const f = env.current()->fees().base;
656 auto const r = reserve(env, 0);
657
658 env.fund(r + f, alice);
659
660 env(trust(alice, usdOffer), ter(tesSUCCESS));
661 env(pay(gw, alice, usdOffer), ter(tesSUCCESS));
662 env(offer(alice, xrpOffer, usdOffer), ter(tecINSUF_RESERVE_OFFER));
663
664 env.require(balance(alice, r - f), owners(alice, 1));
665 }
666
667 // Partial cross:
668 {
669 Env env{*this, features};
670
671 env.fund(XRP(1000000), gw);
672
673 auto const f = env.current()->fees().base;
674 auto const r = reserve(env, 0);
675
676 auto const usdOffer2 = USD(500);
677 auto const xrpOffer2 = XRP(500);
678
679 env.fund(r + f + xrpOffer, bob);
680
681 env(offer(bob, usdOffer2, xrpOffer2), ter(tesSUCCESS));
682 env.fund(r + f, alice);
683
684 env(trust(alice, usdOffer), ter(tesSUCCESS));
685 env(pay(gw, alice, usdOffer), ter(tesSUCCESS));
686 env(offer(alice, xrpOffer, usdOffer), ter(tesSUCCESS));
687
688 env.require(
689 balance(alice, r - f + xrpOffer2),
690 balance(alice, usdOffer2),
691 owners(alice, 1),
692 balance(bob, r + xrpOffer2),
693 balance(bob, usdOffer2),
694 owners(bob, 1));
695 }
696
697 // Account has enough reserve as is, but not enough
698 // if an offer were added. Attempt to sell IOUs to
699 // buy XRP. If it fully crosses, we succeed.
700 {
701 Env env{*this, features};
702
703 env.fund(XRP(1000000), gw);
704
705 auto const f = env.current()->fees().base;
706 auto const r = reserve(env, 0);
707
708 auto const usdOffer2 = USD(500);
709 auto const xrpOffer2 = XRP(500);
710
711 env.fund(r + f + xrpOffer, bob, carol);
712
713 env(offer(bob, usdOffer2, xrpOffer2), ter(tesSUCCESS));
714 env(offer(carol, usdOffer, xrpOffer), ter(tesSUCCESS));
715
716 env.fund(r + f, alice);
717
718 env(trust(alice, usdOffer), ter(tesSUCCESS));
719 env(pay(gw, alice, usdOffer), ter(tesSUCCESS));
720 env(offer(alice, xrpOffer, usdOffer), ter(tesSUCCESS));
721
722 env.require(
723 balance(alice, r - f + xrpOffer),
724 balance(alice, USD(0)),
725 owners(alice, 1),
726 balance(bob, r + xrpOffer2),
727 balance(bob, usdOffer2),
728 owners(bob, 1),
729 balance(carol, r + xrpOffer2),
730 balance(carol, usdOffer2),
731 owners(carol, 2));
732 }
733 }
734
735 // Helper function that returns the Offers on an account.
738 {
741 *env.current(),
742 account,
743 [&result](std::shared_ptr<SLE const> const& sle) {
744 if (sle->getType() == ltOFFER)
745 result.push_back(sle);
746 });
747 return result;
748 }
749
750 void
752 {
753 testcase("Fill Modes");
754
755 using namespace jtx;
756
757 auto const startBalance = XRP(1000000);
758 auto const gw = Account{"gateway"};
759 auto const alice = Account{"alice"};
760 auto const bob = Account{"bob"};
761 auto const USD = gw["USD"];
762
763 // Fill or Kill - unless we fully cross, just charge a fee and don't
764 // place the offer on the books. But also clean up expired offers
765 // that are discovered along the way.
766 {
767 Env env{*this, features};
768
769 auto const f = env.current()->fees().base;
770
771 env.fund(startBalance, gw, alice, bob);
772 env.close();
773
774 // bob creates an offer that expires before the next ledger close.
775 env(offer(bob, USD(500), XRP(500)),
776 json(sfExpiration.fieldName, lastClose(env) + 1),
777 ter(tesSUCCESS));
778
779 // The offer expires (it's not removed yet).
780 env.close();
781 env.require(owners(bob, 1), offers(bob, 1));
782
783 // bob creates the offer that will be crossed.
784 env(offer(bob, USD(500), XRP(500)), ter(tesSUCCESS));
785 env.close();
786 env.require(owners(bob, 2), offers(bob, 2));
787
788 env(trust(alice, USD(1000)), ter(tesSUCCESS));
789 env(pay(gw, alice, USD(1000)), ter(tesSUCCESS));
790
791 // Order that can't be filled but will remove bob's expired offer:
792 {
793 TER const killedCode{TER{tecKILLED}};
794 env(offer(alice, XRP(1000), USD(1000)),
796 ter(killedCode));
797 }
798 env.require(
799 balance(alice, startBalance - (f * 2)),
800 balance(alice, USD(1000)),
801 owners(alice, 1),
802 offers(alice, 0),
803 balance(bob, startBalance - (f * 2)),
804 balance(bob, USD(none)),
805 owners(bob, 1),
806 offers(bob, 1));
807
808 // Order that can be filled
809 env(offer(alice, XRP(500), USD(500)),
811 ter(tesSUCCESS));
812
813 env.require(
814 balance(alice, startBalance - (f * 3) + XRP(500)),
815 balance(alice, USD(500)),
816 owners(alice, 1),
817 offers(alice, 0),
818 balance(bob, startBalance - (f * 2) - XRP(500)),
819 balance(bob, USD(500)),
820 owners(bob, 1),
821 offers(bob, 0));
822 }
823
824 // Immediate or Cancel - cross as much as possible
825 // and add nothing on the books:
826 {
827 Env env{*this, features};
828
829 auto const f = env.current()->fees().base;
830
831 env.fund(startBalance, gw, alice, bob);
832 env.close();
833
834 env(trust(alice, USD(1000)), ter(tesSUCCESS));
835 env(pay(gw, alice, USD(1000)), ter(tesSUCCESS));
836
837 // No cross:
838 {
839 TER const expectedCode = tecKILLED;
840 env(offer(alice, XRP(1000), USD(1000)),
842 ter(expectedCode));
843 }
844
845 env.require(
846 balance(alice, startBalance - f - f),
847 balance(alice, USD(1000)),
848 owners(alice, 1),
849 offers(alice, 0));
850
851 // Partially cross:
852 env(offer(bob, USD(50), XRP(50)), ter(tesSUCCESS));
853 env(offer(alice, XRP(1000), USD(1000)),
855 ter(tesSUCCESS));
856
857 env.require(
858 balance(alice, startBalance - f - f - f + XRP(50)),
859 balance(alice, USD(950)),
860 owners(alice, 1),
861 offers(alice, 0),
862 balance(bob, startBalance - f - XRP(50)),
863 balance(bob, USD(50)),
864 owners(bob, 1),
865 offers(bob, 0));
866
867 // Fully cross:
868 env(offer(bob, USD(50), XRP(50)), ter(tesSUCCESS));
869 env(offer(alice, XRP(50), USD(50)),
871 ter(tesSUCCESS));
872
873 env.require(
874 balance(alice, startBalance - f - f - f - f + XRP(100)),
875 balance(alice, USD(900)),
876 owners(alice, 1),
877 offers(alice, 0),
878 balance(bob, startBalance - f - f - XRP(100)),
879 balance(bob, USD(100)),
880 owners(bob, 1),
881 offers(bob, 0));
882 }
883
884 // tfPassive -- place the offer without crossing it.
885 {
886 Env env(*this, features);
887
888 env.fund(startBalance, gw, alice, bob);
889 env.close();
890
891 env(trust(bob, USD(1000)));
892 env.close();
893
894 env(pay(gw, bob, USD(1000)));
895 env.close();
896
897 env(offer(alice, USD(1000), XRP(2000)));
898 env.close();
899
900 auto const aliceOffers = offersOnAccount(env, alice);
901 BEAST_EXPECT(aliceOffers.size() == 1);
902 for (auto offerPtr : aliceOffers)
903 {
904 auto const& offer = *offerPtr;
905 BEAST_EXPECT(offer[sfTakerGets] == XRP(2000));
906 BEAST_EXPECT(offer[sfTakerPays] == USD(1000));
907 }
908
909 // bob creates a passive offer that could cross alice's.
910 // bob's offer should stay in the ledger.
911 env(offer(bob, XRP(2000), USD(1000), tfPassive));
912 env.close();
913 env.require(offers(alice, 1));
914
915 auto const bobOffers = offersOnAccount(env, bob);
916 BEAST_EXPECT(bobOffers.size() == 1);
917 for (auto offerPtr : bobOffers)
918 {
919 auto const& offer = *offerPtr;
920 BEAST_EXPECT(offer[sfTakerGets] == USD(1000));
921 BEAST_EXPECT(offer[sfTakerPays] == XRP(2000));
922 }
923
924 // It should be possible for gw to cross both of those offers.
925 env(offer(gw, XRP(2000), USD(1000)));
926 env.close();
927 env.require(offers(alice, 0));
928 env.require(offers(gw, 0));
929 env.require(offers(bob, 1));
930
931 env(offer(gw, USD(1000), XRP(2000)));
932 env.close();
933 env.require(offers(bob, 0));
934 env.require(offers(gw, 0));
935 }
936
937 // tfPassive -- cross only offers of better quality.
938 {
939 Env env(*this, features);
940
941 env.fund(startBalance, gw, "alice", "bob");
942 env.close();
943
944 env(trust("bob", USD(1000)));
945 env.close();
946
947 env(pay(gw, "bob", USD(1000)));
948 env(offer("alice", USD(500), XRP(1001)));
949 env.close();
950
951 env(offer("alice", USD(500), XRP(1000)));
952 env.close();
953
954 auto const aliceOffers = offersOnAccount(env, "alice");
955 BEAST_EXPECT(aliceOffers.size() == 2);
956
957 // bob creates a passive offer. That offer should cross one
958 // of alice's (the one with better quality) and leave alice's
959 // other offer untouched.
960 env(offer("bob", XRP(2000), USD(1000), tfPassive));
961 env.close();
962 env.require(offers("alice", 1));
963
964 auto const bobOffers = offersOnAccount(env, "bob");
965 BEAST_EXPECT(bobOffers.size() == 1);
966 for (auto offerPtr : bobOffers)
967 {
968 auto const& offer = *offerPtr;
969 BEAST_EXPECT(offer[sfTakerGets] == USD(499.5));
970 BEAST_EXPECT(offer[sfTakerPays] == XRP(999));
971 }
972 }
973 }
974
975 void
977 {
978 testcase("Malformed Detection");
979
980 using namespace jtx;
981
982 auto const startBalance = XRP(1000000);
983 auto const gw = Account{"gateway"};
984 auto const alice = Account{"alice"};
985 auto const USD = gw["USD"];
986
987 Env env{*this, features};
988
989 env.fund(startBalance, gw, alice);
990 env.close();
991
992 // Order that has invalid flags
993 env(offer(alice, USD(1000), XRP(1000)),
996 env.require(
997 balance(alice, startBalance), owners(alice, 0), offers(alice, 0));
998
999 // Order with incompatible flags
1000 env(offer(alice, USD(1000), XRP(1000)),
1003 env.require(
1004 balance(alice, startBalance), owners(alice, 0), offers(alice, 0));
1005
1006 // Sell and buy the same asset
1007 {
1008 // Alice tries an XRP to XRP order:
1009 env(offer(alice, XRP(1000), XRP(1000)), ter(temBAD_OFFER));
1010 env.require(owners(alice, 0), offers(alice, 0));
1011
1012 // Alice tries an IOU to IOU order:
1013 env(trust(alice, USD(1000)), ter(tesSUCCESS));
1014 env(pay(gw, alice, USD(1000)), ter(tesSUCCESS));
1015 env(offer(alice, USD(1000), USD(1000)), ter(temREDUNDANT));
1016 env.require(owners(alice, 1), offers(alice, 0));
1017 }
1018
1019 // Offers with negative amounts
1020 {
1021 env(offer(alice, -USD(1000), XRP(1000)), ter(temBAD_OFFER));
1022 env.require(owners(alice, 1), offers(alice, 0));
1023
1024 env(offer(alice, USD(1000), -XRP(1000)), ter(temBAD_OFFER));
1025 env.require(owners(alice, 1), offers(alice, 0));
1026 }
1027
1028 // Offer with a bad expiration
1029 {
1030 env(offer(alice, USD(1000), XRP(1000)),
1031 json(sfExpiration.fieldName, std::uint32_t(0)),
1033 env.require(owners(alice, 1), offers(alice, 0));
1034 }
1035
1036 // Offer with a bad offer sequence
1037 {
1038 env(offer(alice, USD(1000), XRP(1000)),
1039 json(jss::OfferSequence, std::uint32_t(0)),
1041 env.require(owners(alice, 1), offers(alice, 0));
1042 }
1043
1044 // Use XRP as a currency code
1045 {
1046 auto const BAD = IOU(gw, badCurrency());
1047
1048 env(offer(alice, XRP(1000), BAD(1000)), ter(temBAD_CURRENCY));
1049 env.require(owners(alice, 1), offers(alice, 0));
1050 }
1051 }
1052
1053 void
1055 {
1056 testcase("Offer Expiration");
1057
1058 using namespace jtx;
1059
1060 auto const gw = Account{"gateway"};
1061 auto const alice = Account{"alice"};
1062 auto const bob = Account{"bob"};
1063 auto const USD = gw["USD"];
1064
1065 auto const startBalance = XRP(1000000);
1066 auto const usdOffer = USD(1000);
1067 auto const xrpOffer = XRP(1000);
1068
1069 Env env{*this, features};
1070
1071 env.fund(startBalance, gw, alice, bob);
1072 env.close();
1073
1074 auto const f = env.current()->fees().base;
1075
1076 env(trust(alice, usdOffer), ter(tesSUCCESS));
1077 env(pay(gw, alice, usdOffer), ter(tesSUCCESS));
1078 env.close();
1079 env.require(
1080 balance(alice, startBalance - f),
1081 balance(alice, usdOffer),
1082 offers(alice, 0),
1083 owners(alice, 1));
1084
1085 env(offer(alice, xrpOffer, usdOffer),
1086 json(sfExpiration.fieldName, lastClose(env)),
1087 ter(tecEXPIRED));
1088
1089 env.require(
1090 balance(alice, startBalance - f - f),
1091 balance(alice, usdOffer),
1092 offers(alice, 0),
1093 owners(alice, 1));
1094 env.close();
1095
1096 // Add an offer that expires before the next ledger close
1097 env(offer(alice, xrpOffer, usdOffer),
1098 json(sfExpiration.fieldName, lastClose(env) + 1),
1099 ter(tesSUCCESS));
1100 env.require(
1101 balance(alice, startBalance - f - f - f),
1102 balance(alice, usdOffer),
1103 offers(alice, 1),
1104 owners(alice, 2));
1105
1106 // The offer expires (it's not removed yet)
1107 env.close();
1108 env.require(
1109 balance(alice, startBalance - f - f - f),
1110 balance(alice, usdOffer),
1111 offers(alice, 1),
1112 owners(alice, 2));
1113
1114 // Add offer - the expired offer is removed
1115 env(offer(bob, usdOffer, xrpOffer), ter(tesSUCCESS));
1116 env.require(
1117 balance(alice, startBalance - f - f - f),
1118 balance(alice, usdOffer),
1119 offers(alice, 0),
1120 owners(alice, 1),
1121 balance(bob, startBalance - f),
1122 balance(bob, USD(none)),
1123 offers(bob, 1),
1124 owners(bob, 1));
1125 }
1126
1127 void
1129 {
1130 testcase("Unfunded Crossing");
1131
1132 using namespace jtx;
1133
1134 auto const gw = Account{"gateway"};
1135 auto const USD = gw["USD"];
1136
1137 auto const usdOffer = USD(1000);
1138 auto const xrpOffer = XRP(1000);
1139
1140 Env env{*this, features};
1141
1142 env.fund(XRP(1000000), gw);
1143 env.close();
1144
1145 // The fee that's charged for transactions
1146 auto const f = env.current()->fees().base;
1147
1148 // Account is at the reserve, and will dip below once
1149 // fees are subtracted.
1150 env.fund(reserve(env, 0), "alice");
1151 env.close();
1152 env(offer("alice", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER));
1153 env.require(balance("alice", reserve(env, 0) - f), owners("alice", 0));
1154
1155 // Account has just enough for the reserve and the
1156 // fee.
1157 env.fund(reserve(env, 0) + f, "bob");
1158 env.close();
1159 env(offer("bob", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER));
1160 env.require(balance("bob", reserve(env, 0)), owners("bob", 0));
1161
1162 // Account has enough for the reserve, the fee and
1163 // the offer, and a bit more, but not enough for the
1164 // reserve after the offer is placed.
1165 env.fund(reserve(env, 0) + f + XRP(1), "carol");
1166 env.close();
1167 env(offer("carol", usdOffer, xrpOffer), ter(tecINSUF_RESERVE_OFFER));
1168 env.require(
1169 balance("carol", reserve(env, 0) + XRP(1)), owners("carol", 0));
1170
1171 // Account has enough for the reserve plus one
1172 // offer, and the fee.
1173 env.fund(reserve(env, 1) + f, "dan");
1174 env.close();
1175 env(offer("dan", usdOffer, xrpOffer), ter(tesSUCCESS));
1176 env.require(balance("dan", reserve(env, 1)), owners("dan", 1));
1177
1178 // Account has enough for the reserve plus one
1179 // offer, the fee and the entire offer amount.
1180 env.fund(reserve(env, 1) + f + xrpOffer, "eve");
1181 env.close();
1182 env(offer("eve", usdOffer, xrpOffer), ter(tesSUCCESS));
1183 env.require(
1184 balance("eve", reserve(env, 1) + xrpOffer), owners("eve", 1));
1185 }
1186
1187 void
1188 testSelfCross(bool use_partner, FeatureBitset features)
1189 {
1190 testcase(
1191 std::string("Self-crossing") +
1192 (use_partner ? ", with partner account" : ""));
1193
1194 using namespace jtx;
1195
1196 auto const gw = Account{"gateway"};
1197 auto const partner = Account{"partner"};
1198 auto const USD = gw["USD"];
1199 auto const BTC = gw["BTC"];
1200
1201 Env env{*this, features};
1202 env.close();
1203
1204 env.fund(XRP(10000), gw);
1205 if (use_partner)
1206 {
1207 env.fund(XRP(10000), partner);
1208 env.close();
1209 env(trust(partner, USD(100)));
1210 env(trust(partner, BTC(500)));
1211 env.close();
1212 env(pay(gw, partner, USD(100)));
1213 env(pay(gw, partner, BTC(500)));
1214 }
1215 auto const& account_to_test = use_partner ? partner : gw;
1216
1217 env.close();
1218 env.require(offers(account_to_test, 0));
1219
1220 // PART 1:
1221 // we will make two offers that can be used to bridge BTC to USD
1222 // through XRP
1223 env(offer(account_to_test, BTC(250), XRP(1000)));
1224 env.require(offers(account_to_test, 1));
1225
1226 // validate that the book now shows a BTC for XRP offer
1227 BEAST_EXPECT(isOffer(env, account_to_test, BTC(250), XRP(1000)));
1228
1229 auto const secondLegSeq = env.seq(account_to_test);
1230 env(offer(account_to_test, XRP(1000), USD(50)));
1231 env.require(offers(account_to_test, 2));
1232
1233 // validate that the book also shows a XRP for USD offer
1234 BEAST_EXPECT(isOffer(env, account_to_test, XRP(1000), USD(50)));
1235
1236 // now make an offer that will cross and auto-bridge, meaning
1237 // the outstanding offers will be taken leaving us with none
1238 env(offer(account_to_test, USD(50), BTC(250)));
1239
1240 auto jrr = getBookOffers(env, USD, BTC);
1241 BEAST_EXPECT(jrr[jss::offers].isArray());
1242 BEAST_EXPECT(jrr[jss::offers].size() == 0);
1243
1244 jrr = getBookOffers(env, BTC, XRP);
1245 BEAST_EXPECT(jrr[jss::offers].isArray());
1246 BEAST_EXPECT(jrr[jss::offers].size() == 0);
1247
1248 // NOTE :
1249 // At this point, all offers are expected to be consumed.
1250 {
1251 auto acctOffers = offersOnAccount(env, account_to_test);
1252
1253 BEAST_EXPECT(acctOffers.size() == 0);
1254 for (auto const& offerPtr : acctOffers)
1255 {
1256 auto const& offer = *offerPtr;
1257 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
1258 BEAST_EXPECT(offer[sfTakerGets] == USD(0));
1259 BEAST_EXPECT(offer[sfTakerPays] == XRP(0));
1260 }
1261 }
1262
1263 // cancel that lingering second offer so that it doesn't interfere
1264 // with the next set of offers we test. This will not be needed once
1265 // the bridging bug is fixed
1266 env(offer_cancel(account_to_test, secondLegSeq));
1267 env.require(offers(account_to_test, 0));
1268
1269 // PART 2:
1270 // simple direct crossing BTC to USD and then USD to BTC which causes
1271 // the first offer to be replaced
1272 env(offer(account_to_test, BTC(250), USD(50)));
1273 env.require(offers(account_to_test, 1));
1274
1275 // validate that the book shows one BTC for USD offer and no USD for
1276 // BTC offers
1277 BEAST_EXPECT(isOffer(env, account_to_test, BTC(250), USD(50)));
1278
1279 jrr = getBookOffers(env, USD, BTC);
1280 BEAST_EXPECT(jrr[jss::offers].isArray());
1281 BEAST_EXPECT(jrr[jss::offers].size() == 0);
1282
1283 // this second offer would self-cross directly, so it causes the first
1284 // offer by the same owner/taker to be removed
1285 env(offer(account_to_test, USD(50), BTC(250)));
1286 env.require(offers(account_to_test, 1));
1287
1288 // validate that we now have just the second offer...the first
1289 // was removed
1290 jrr = getBookOffers(env, BTC, USD);
1291 BEAST_EXPECT(jrr[jss::offers].isArray());
1292 BEAST_EXPECT(jrr[jss::offers].size() == 0);
1293
1294 BEAST_EXPECT(isOffer(env, account_to_test, USD(50), BTC(250)));
1295 }
1296
1297 void
1299 {
1300 // This test creates an offer test for negative balance
1301 // with transfer fees and minuscule funds.
1302 testcase("Negative Balance");
1303
1304 using namespace jtx;
1305
1306 // This is one of the few tests where fixReducedOffersV2 changes the
1307 // results. So test both with and without fixReducedOffersV2.
1308 for (FeatureBitset localFeatures :
1309 {features - fixReducedOffersV2, features | fixReducedOffersV2})
1310 {
1311 Env env{*this, localFeatures};
1312
1313 auto const gw = Account{"gateway"};
1314 auto const alice = Account{"alice"};
1315 auto const bob = Account{"bob"};
1316 auto const USD = gw["USD"];
1317 auto const BTC = gw["BTC"];
1318
1319 // these *interesting* amounts were taken
1320 // from the original JS test that was ported here
1321 auto const gw_initial_balance = drops(1149999730);
1322 auto const alice_initial_balance = drops(499946999680);
1323 auto const bob_initial_balance = drops(10199999920);
1324 auto const small_amount =
1325 STAmount{bob["USD"].issue(), UINT64_C(2710505431213761), -33};
1326
1327 env.fund(gw_initial_balance, gw);
1328 env.fund(alice_initial_balance, alice);
1329 env.fund(bob_initial_balance, bob);
1330 env.close();
1331
1332 env(rate(gw, 1.005));
1333
1334 env(trust(alice, USD(500)));
1335 env(trust(bob, USD(50)));
1336 env(trust(gw, alice["USD"](100)));
1337
1338 env(pay(gw, alice, alice["USD"](50)));
1339 env(pay(gw, bob, small_amount));
1340
1341 env(offer(alice, USD(50), XRP(150000)));
1342
1343 // unfund the offer
1344 env(pay(alice, gw, USD(100)));
1345
1346 // drop the trust line (set to 0)
1347 env(trust(gw, alice["USD"](0)));
1348
1349 // verify balances
1350 auto jrr = ledgerEntryState(env, alice, gw, "USD");
1351 BEAST_EXPECT(
1352 jrr[jss::node][sfBalance.fieldName][jss::value] == "50");
1353
1354 jrr = ledgerEntryState(env, bob, gw, "USD");
1355 BEAST_EXPECT(
1356 jrr[jss::node][sfBalance.fieldName][jss::value] ==
1357 "-2710505431213761e-33");
1358
1359 // create crossing offer
1360 std::uint32_t const bobOfferSeq = env.seq(bob);
1361 env(offer(bob, XRP(2000), USD(1)));
1362
1363 if (localFeatures[fixReducedOffersV2])
1364 {
1365 // With the rounding introduced by fixReducedOffersV2, bob's
1366 // offer does not cross alice's offer and goes straight into
1367 // the ledger.
1368 jrr = ledgerEntryState(env, bob, gw, "USD");
1369 BEAST_EXPECT(
1370 jrr[jss::node][sfBalance.fieldName][jss::value] ==
1371 "-2710505431213761e-33");
1372
1373 Json::Value const bobOffer =
1374 ledgerEntryOffer(env, bob, bobOfferSeq)[jss::node];
1375 BEAST_EXPECT(bobOffer[sfTakerGets.jsonName][jss::value] == "1");
1376 BEAST_EXPECT(bobOffer[sfTakerPays.jsonName] == "2000000000");
1377 return;
1378 }
1379
1380 // verify balances again.
1381 //
1382 // NOTE:
1383 // Here a difference in the rounding modes of our two offer
1384 // crossing algorithms becomes apparent. The old offer crossing
1385 // would consume small_amount and transfer no XRP. The new offer
1386 // crossing transfers a single drop, rather than no drops.
1387 auto const crossingDelta = drops(1);
1388
1389 jrr = ledgerEntryState(env, alice, gw, "USD");
1390 BEAST_EXPECT(
1391 jrr[jss::node][sfBalance.fieldName][jss::value] == "50");
1392 BEAST_EXPECT(
1393 env.balance(alice, xrpIssue()) ==
1394 alice_initial_balance - env.current()->fees().base * 3 -
1395 crossingDelta);
1396
1397 jrr = ledgerEntryState(env, bob, gw, "USD");
1398 BEAST_EXPECT(
1399 jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
1400 BEAST_EXPECT(
1401 env.balance(bob, xrpIssue()) ==
1402 bob_initial_balance - env.current()->fees().base * 2 +
1403 crossingDelta);
1404 }
1405 }
1406
1407 void
1408 testOfferCrossWithXRP(bool reverse_order, FeatureBitset features)
1409 {
1410 testcase(
1411 std::string("Offer Crossing with XRP, ") +
1412 (reverse_order ? "Reverse" : "Normal") + " order");
1413
1414 using namespace jtx;
1415
1416 Env env{*this, features};
1417
1418 auto const gw = Account{"gateway"};
1419 auto const alice = Account{"alice"};
1420 auto const bob = Account{"bob"};
1421 auto const USD = gw["USD"];
1422
1423 env.fund(XRP(10000), gw, alice, bob);
1424 env.close();
1425
1426 env(trust(alice, USD(1000)));
1427 env(trust(bob, USD(1000)));
1428
1429 env(pay(gw, alice, alice["USD"](500)));
1430
1431 if (reverse_order)
1432 env(offer(bob, USD(1), XRP(4000)));
1433
1434 env(offer(alice, XRP(150000), USD(50)));
1435
1436 if (!reverse_order)
1437 env(offer(bob, USD(1), XRP(4000)));
1438
1439 // Existing offer pays better than this wants.
1440 // Fully consume existing offer.
1441 // Pay 1 USD, get 4000 XRP.
1442
1443 auto jrr = ledgerEntryState(env, bob, gw, "USD");
1444 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-1");
1445 jrr = ledgerEntryRoot(env, bob);
1446 BEAST_EXPECT(
1447 jrr[jss::node][sfBalance.fieldName] ==
1448 to_string((XRP(10000) - XRP(reverse_order ? 4000 : 3000) -
1449 env.current()->fees().base * 2)
1450 .xrp()));
1451
1452 jrr = ledgerEntryState(env, alice, gw, "USD");
1453 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-499");
1454 jrr = ledgerEntryRoot(env, alice);
1455 BEAST_EXPECT(
1456 jrr[jss::node][sfBalance.fieldName] ==
1457 to_string((XRP(10000) + XRP(reverse_order ? 4000 : 3000) -
1458 env.current()->fees().base * 2)
1459 .xrp()));
1460 }
1461
1462 void
1464 {
1465 testcase("Offer Crossing with Limit Override");
1466
1467 using namespace jtx;
1468
1469 Env env{*this, features};
1470
1471 auto const gw = Account{"gateway"};
1472 auto const alice = Account{"alice"};
1473 auto const bob = Account{"bob"};
1474 auto const USD = gw["USD"];
1475
1476 env.fund(XRP(100000), gw, alice, bob);
1477 env.close();
1478
1479 env(trust(alice, USD(1000)));
1480
1481 env(pay(gw, alice, alice["USD"](500)));
1482
1483 env(offer(alice, XRP(150000), USD(50)));
1484 env(offer(bob, USD(1), XRP(3000)));
1485
1486 auto jrr = ledgerEntryState(env, bob, gw, "USD");
1487 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-1");
1488 jrr = ledgerEntryRoot(env, bob);
1489 BEAST_EXPECT(
1490 jrr[jss::node][sfBalance.fieldName] ==
1491 to_string((XRP(100000) - XRP(3000) - env.current()->fees().base * 1)
1492 .xrp()));
1493
1494 jrr = ledgerEntryState(env, alice, gw, "USD");
1495 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-499");
1496 jrr = ledgerEntryRoot(env, alice);
1497 BEAST_EXPECT(
1498 jrr[jss::node][sfBalance.fieldName] ==
1499 to_string((XRP(100000) + XRP(3000) - env.current()->fees().base * 2)
1500 .xrp()));
1501 }
1502
1503 void
1505 {
1506 testcase("Offer Accept then Cancel.");
1507
1508 using namespace jtx;
1509
1510 Env env{*this, features};
1511
1512 auto const USD = env.master["USD"];
1513
1514 auto const nextOfferSeq = env.seq(env.master);
1515 env(offer(env.master, XRP(500), USD(100)));
1516 env.close();
1517
1518 env(offer_cancel(env.master, nextOfferSeq));
1519 BEAST_EXPECT(env.seq(env.master) == nextOfferSeq + 2);
1520
1521 // ledger_accept, call twice and verify no odd behavior
1522 env.close();
1523 env.close();
1524 BEAST_EXPECT(env.seq(env.master) == nextOfferSeq + 2);
1525 }
1526
1527 void
1529 {
1530 testcase("Offer Cancel Past and Future Sequence.");
1531
1532 using namespace jtx;
1533
1534 Env env{*this, features};
1535
1536 auto const alice = Account{"alice"};
1537
1538 auto const nextOfferSeq = env.seq(env.master);
1539 env.fund(XRP(10000), alice);
1540 env.close();
1541
1542 env(offer_cancel(env.master, nextOfferSeq));
1543
1544 env(offer_cancel(env.master, env.seq(env.master)),
1546
1547 env(offer_cancel(env.master, env.seq(env.master) + 1),
1549
1550 env.close();
1551 }
1552
1553 void
1555 {
1556 testcase("Currency Conversion: Entire Offer");
1557
1558 using namespace jtx;
1559
1560 Env env{*this, features};
1561
1562 auto const gw = Account{"gateway"};
1563 auto const alice = Account{"alice"};
1564 auto const bob = Account{"bob"};
1565 auto const USD = gw["USD"];
1566
1567 env.fund(XRP(10000), gw, alice, bob);
1568 env.close();
1569 env.require(owners(bob, 0));
1570
1571 env(trust(alice, USD(100)));
1572 env(trust(bob, USD(1000)));
1573
1574 env.require(owners(alice, 1), owners(bob, 1));
1575
1576 env(pay(gw, alice, alice["USD"](100)));
1577 auto const bobOfferSeq = env.seq(bob);
1578 env(offer(bob, USD(100), XRP(500)));
1579
1580 env.require(owners(alice, 1), owners(bob, 2));
1581 auto jro = ledgerEntryOffer(env, bob, bobOfferSeq);
1582 BEAST_EXPECT(
1583 jro[jss::node][jss::TakerGets] == XRP(500).value().getText());
1584 BEAST_EXPECT(
1585 jro[jss::node][jss::TakerPays] ==
1586 USD(100).value().getJson(JsonOptions::none));
1587
1588 env(pay(alice, alice, XRP(500)), sendmax(USD(100)));
1589
1590 auto jrr = ledgerEntryState(env, alice, gw, "USD");
1591 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
1592 jrr = ledgerEntryRoot(env, alice);
1593 BEAST_EXPECT(
1594 jrr[jss::node][sfBalance.fieldName] ==
1595 to_string((XRP(10000) + XRP(500) - env.current()->fees().base * 2)
1596 .xrp()));
1597
1598 jrr = ledgerEntryState(env, bob, gw, "USD");
1599 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-100");
1600
1601 jro = ledgerEntryOffer(env, bob, bobOfferSeq);
1602 BEAST_EXPECT(jro[jss::error] == "entryNotFound");
1603
1604 env.require(owners(alice, 1), owners(bob, 1));
1605 }
1606
1607 void
1609 {
1610 testcase("Currency Conversion: Offerer Into Debt");
1611
1612 using namespace jtx;
1613
1614 Env env{*this, features};
1615
1616 auto const alice = Account{"alice"};
1617 auto const bob = Account{"bob"};
1618 auto const carol = Account{"carol"};
1619
1620 env.fund(XRP(10000), alice, bob, carol);
1621 env.close();
1622
1623 env(trust(alice, carol["EUR"](2000)));
1624 env(trust(bob, alice["USD"](100)));
1625 env(trust(carol, bob["EUR"](1000)));
1626
1627 auto const bobOfferSeq = env.seq(bob);
1628 env(offer(bob, alice["USD"](50), carol["EUR"](200)),
1630
1631 env(offer(alice, carol["EUR"](200), alice["USD"](50)));
1632
1633 auto jro = ledgerEntryOffer(env, bob, bobOfferSeq);
1634 BEAST_EXPECT(jro[jss::error] == "entryNotFound");
1635 }
1636
1637 void
1639 {
1640 testcase("Currency Conversion: In Parts");
1641
1642 using namespace jtx;
1643
1644 Env env{*this, features};
1645
1646 auto const gw = Account{"gateway"};
1647 auto const alice = Account{"alice"};
1648 auto const bob = Account{"bob"};
1649 auto const USD = gw["USD"];
1650
1651 env.fund(XRP(10000), gw, alice, bob);
1652 env.close();
1653
1654 env(trust(alice, USD(200)));
1655 env(trust(bob, USD(1000)));
1656
1657 env(pay(gw, alice, alice["USD"](200)));
1658
1659 auto const bobOfferSeq = env.seq(bob);
1660 env(offer(bob, USD(100), XRP(500)));
1661
1662 env(pay(alice, alice, XRP(200)), sendmax(USD(100)));
1663
1664 // The previous payment reduced the remaining offer amount by 200 XRP
1665 auto jro = ledgerEntryOffer(env, bob, bobOfferSeq);
1666 BEAST_EXPECT(
1667 jro[jss::node][jss::TakerGets] == XRP(300).value().getText());
1668 BEAST_EXPECT(
1669 jro[jss::node][jss::TakerPays] ==
1670 USD(60).value().getJson(JsonOptions::none));
1671
1672 // the balance between alice and gw is 160 USD..200 less the 40 taken
1673 // by the offer
1674 auto jrr = ledgerEntryState(env, alice, gw, "USD");
1675 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-160");
1676 // alice now has 200 more XRP from the payment
1677 jrr = ledgerEntryRoot(env, alice);
1678 BEAST_EXPECT(
1679 jrr[jss::node][sfBalance.fieldName] ==
1680 to_string((XRP(10000) + XRP(200) - env.current()->fees().base * 2)
1681 .xrp()));
1682
1683 // bob got 40 USD from partial consumption of the offer
1684 jrr = ledgerEntryState(env, bob, gw, "USD");
1685 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-40");
1686
1687 // Alice converts USD to XRP which should fail
1688 // due to PartialPayment.
1689 env(pay(alice, alice, XRP(600)),
1690 sendmax(USD(100)),
1692
1693 // Alice converts USD to XRP, should succeed because
1694 // we permit partial payment
1695 env(pay(alice, alice, XRP(600)),
1696 sendmax(USD(100)),
1698
1699 // Verify the offer was consumed
1700 jro = ledgerEntryOffer(env, bob, bobOfferSeq);
1701 BEAST_EXPECT(jro[jss::error] == "entryNotFound");
1702
1703 // verify balances look right after the partial payment
1704 // only 300 XRP should be have been payed since that's all
1705 // that remained in the offer from bob. The alice balance is now
1706 // 100 USD because another 60 USD were transferred to bob in the second
1707 // payment
1708 jrr = ledgerEntryState(env, alice, gw, "USD");
1709 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-100");
1710 jrr = ledgerEntryRoot(env, alice);
1711 BEAST_EXPECT(
1712 jrr[jss::node][sfBalance.fieldName] ==
1713 to_string((XRP(10000) + XRP(200) + XRP(300) -
1714 env.current()->fees().base * 4)
1715 .xrp()));
1716
1717 // bob now has 100 USD - 40 from the first payment and 60 from the
1718 // second (partial) payment
1719 jrr = ledgerEntryState(env, bob, gw, "USD");
1720 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-100");
1721 }
1722
1723 void
1725 {
1726 testcase("Cross Currency Payment: Start with XRP");
1727
1728 using namespace jtx;
1729
1730 Env env{*this, features};
1731
1732 auto const gw = Account{"gateway"};
1733 auto const alice = Account{"alice"};
1734 auto const bob = Account{"bob"};
1735 auto const carol = Account{"carol"};
1736 auto const USD = gw["USD"];
1737
1738 env.fund(XRP(10000), gw, alice, bob, carol);
1739 env.close();
1740
1741 env(trust(carol, USD(1000)));
1742 env(trust(bob, USD(2000)));
1743
1744 env(pay(gw, carol, carol["USD"](500)));
1745
1746 auto const carolOfferSeq = env.seq(carol);
1747 env(offer(carol, XRP(500), USD(50)));
1748
1749 env(pay(alice, bob, USD(25)), sendmax(XRP(333)));
1750
1751 auto jrr = ledgerEntryState(env, bob, gw, "USD");
1752 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-25");
1753
1754 jrr = ledgerEntryState(env, carol, gw, "USD");
1755 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-475");
1756
1757 auto jro = ledgerEntryOffer(env, carol, carolOfferSeq);
1758 BEAST_EXPECT(
1759 jro[jss::node][jss::TakerGets] ==
1760 USD(25).value().getJson(JsonOptions::none));
1761 BEAST_EXPECT(
1762 jro[jss::node][jss::TakerPays] == XRP(250).value().getText());
1763 }
1764
1765 void
1767 {
1768 testcase("Cross Currency Payment: End with XRP");
1769
1770 using namespace jtx;
1771
1772 Env env{*this, features};
1773
1774 auto const gw = Account{"gateway"};
1775 auto const alice = Account{"alice"};
1776 auto const bob = Account{"bob"};
1777 auto const carol = Account{"carol"};
1778 auto const USD = gw["USD"];
1779
1780 env.fund(XRP(10000), gw, alice, bob, carol);
1781 env.close();
1782
1783 env(trust(alice, USD(1000)));
1784 env(trust(carol, USD(2000)));
1785
1786 env(pay(gw, alice, alice["USD"](500)));
1787
1788 auto const carolOfferSeq = env.seq(carol);
1789 env(offer(carol, USD(50), XRP(500)));
1790
1791 env(pay(alice, bob, XRP(250)), sendmax(USD(333)));
1792
1793 auto jrr = ledgerEntryState(env, alice, gw, "USD");
1794 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-475");
1795
1796 jrr = ledgerEntryState(env, carol, gw, "USD");
1797 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-25");
1798
1799 jrr = ledgerEntryRoot(env, bob);
1800 BEAST_EXPECT(
1801 jrr[jss::node][sfBalance.fieldName] ==
1803 XRP(10000).value().mantissa() + XRP(250).value().mantissa()));
1804
1805 auto jro = ledgerEntryOffer(env, carol, carolOfferSeq);
1806 BEAST_EXPECT(
1807 jro[jss::node][jss::TakerGets] == XRP(250).value().getText());
1808 BEAST_EXPECT(
1809 jro[jss::node][jss::TakerPays] ==
1810 USD(25).value().getJson(JsonOptions::none));
1811 }
1812
1813 void
1815 {
1816 testcase("Cross Currency Payment: Bridged");
1817
1818 using namespace jtx;
1819
1820 Env env{*this, features};
1821
1822 auto const gw1 = Account{"gateway_1"};
1823 auto const gw2 = Account{"gateway_2"};
1824 auto const alice = Account{"alice"};
1825 auto const bob = Account{"bob"};
1826 auto const carol = Account{"carol"};
1827 auto const dan = Account{"dan"};
1828 auto const USD = gw1["USD"];
1829 auto const EUR = gw2["EUR"];
1830
1831 env.fund(XRP(10000), gw1, gw2, alice, bob, carol, dan);
1832 env.close();
1833
1834 env(trust(alice, USD(1000)));
1835 env(trust(bob, EUR(1000)));
1836 env(trust(carol, USD(1000)));
1837 env(trust(dan, EUR(1000)));
1838
1839 env(pay(gw1, alice, alice["USD"](500)));
1840 env(pay(gw2, dan, dan["EUR"](400)));
1841
1842 auto const carolOfferSeq = env.seq(carol);
1843 env(offer(carol, USD(50), XRP(500)));
1844
1845 auto const danOfferSeq = env.seq(dan);
1846 env(offer(dan, XRP(500), EUR(50)));
1847
1849 jtp[0u][0u][jss::currency] = "XRP";
1850 env(pay(alice, bob, EUR(30)), json(jss::Paths, jtp), sendmax(USD(333)));
1851
1852 auto jrr = ledgerEntryState(env, alice, gw1, "USD");
1853 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "470");
1854
1855 jrr = ledgerEntryState(env, bob, gw2, "EUR");
1856 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-30");
1857
1858 jrr = ledgerEntryState(env, carol, gw1, "USD");
1859 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-30");
1860
1861 jrr = ledgerEntryState(env, dan, gw2, "EUR");
1862 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-370");
1863
1864 auto jro = ledgerEntryOffer(env, carol, carolOfferSeq);
1865 BEAST_EXPECT(
1866 jro[jss::node][jss::TakerGets] == XRP(200).value().getText());
1867 BEAST_EXPECT(
1868 jro[jss::node][jss::TakerPays] ==
1869 USD(20).value().getJson(JsonOptions::none));
1870
1871 jro = ledgerEntryOffer(env, dan, danOfferSeq);
1872 BEAST_EXPECT(
1873 jro[jss::node][jss::TakerGets] ==
1874 gw2["EUR"](20).value().getJson(JsonOptions::none));
1875 BEAST_EXPECT(
1876 jro[jss::node][jss::TakerPays] == XRP(200).value().getText());
1877 }
1878
1879 void
1881 {
1882 // At least with Taker bridging, a sensitivity was identified if the
1883 // second leg goes dry before the first one. This test exercises that
1884 // case.
1885 testcase("Auto Bridged Second Leg Dry");
1886
1887 using namespace jtx;
1888 Env env(*this, features);
1889
1890 Account const alice{"alice"};
1891 Account const bob{"bob"};
1892 Account const carol{"carol"};
1893 Account const gw{"gateway"};
1894 auto const USD = gw["USD"];
1895 auto const EUR = gw["EUR"];
1896
1897 env.fund(XRP(100000000), alice, bob, carol, gw);
1898 env.close();
1899
1900 env.trust(USD(10), alice);
1901 env.close();
1902 env(pay(gw, alice, USD(10)));
1903 env.trust(USD(10), carol);
1904 env.close();
1905 env(pay(gw, carol, USD(3)));
1906
1907 env(offer(alice, EUR(2), XRP(1)));
1908 env(offer(alice, EUR(2), XRP(1)));
1909
1910 env(offer(alice, XRP(1), USD(4)));
1911 env(offer(carol, XRP(1), USD(3)));
1912 env.close();
1913
1914 // Bob offers to buy 10 USD for 10 EUR.
1915 // 1. He spends 2 EUR taking Alice's auto-bridged offers and
1916 // gets 4 USD for that.
1917 // 2. He spends another 2 EUR taking Alice's last EUR->XRP offer and
1918 // Carol's XRP-USD offer. He gets 3 USD for that.
1919 // The key for this test is that Alice's XRP->USD leg goes dry before
1920 // Alice's EUR->XRP. The XRP->USD leg is the second leg which showed
1921 // some sensitivity.
1922 env.trust(EUR(10), bob);
1923 env.close();
1924 env(pay(gw, bob, EUR(10)));
1925 env.close();
1926 env(offer(bob, USD(10), EUR(10)));
1927 env.close();
1928
1929 env.require(balance(bob, USD(7)));
1930 env.require(balance(bob, EUR(6)));
1931 env.require(offers(bob, 1));
1932 env.require(owners(bob, 3));
1933
1934 env.require(balance(alice, USD(6)));
1935 env.require(balance(alice, EUR(4)));
1936 env.require(offers(alice, 0));
1937 env.require(owners(alice, 2));
1938
1939 env.require(balance(carol, USD(0)));
1940 env.require(balance(carol, EUR(none)));
1941
1942 env.require(offers(carol, 0));
1943 env.require(owners(carol, 1));
1944 }
1945
1946 void
1948 {
1949 testcase("Offer Fees Consume Funds");
1950
1951 using namespace jtx;
1952
1953 Env env{*this, features};
1954
1955 auto const gw1 = Account{"gateway_1"};
1956 auto const gw2 = Account{"gateway_2"};
1957 auto const gw3 = Account{"gateway_3"};
1958 auto const alice = Account{"alice"};
1959 auto const bob = Account{"bob"};
1960 auto const USD1 = gw1["USD"];
1961 auto const USD2 = gw2["USD"];
1962 auto const USD3 = gw3["USD"];
1963
1964 // Provide micro amounts to compensate for fees to make results round
1965 // nice.
1966 // reserve: Alice has 3 entries in the ledger, via trust lines
1967 // fees:
1968 // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) +
1969 // 1 for payment == 4
1970 auto const starting_xrp = XRP(100) +
1971 env.current()->fees().accountReserve(3) +
1972 env.current()->fees().base * 4;
1973
1974 env.fund(starting_xrp, gw1, gw2, gw3, alice, bob);
1975 env.close();
1976
1977 env(trust(alice, USD1(1000)));
1978 env(trust(alice, USD2(1000)));
1979 env(trust(alice, USD3(1000)));
1980 env(trust(bob, USD1(1000)));
1981 env(trust(bob, USD2(1000)));
1982
1983 env(pay(gw1, bob, bob["USD"](500)));
1984
1985 env(offer(bob, XRP(200), USD1(200)));
1986 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
1987 // Ask for more than available to prove reserve works.
1988 env(offer(alice, USD1(200), XRP(200)));
1989
1990 auto jrr = ledgerEntryState(env, alice, gw1, "USD");
1991 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "100");
1992 jrr = ledgerEntryRoot(env, alice);
1993 BEAST_EXPECT(
1994 jrr[jss::node][sfBalance.fieldName] ==
1995 STAmount(env.current()->fees().accountReserve(3)).getText());
1996
1997 jrr = ledgerEntryState(env, bob, gw1, "USD");
1998 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-400");
1999 }
2000
2001 void
2003 {
2004 testcase("Offer Create, then Cross");
2005
2006 using namespace jtx;
2007
2008 for (auto NumberSwitchOver : {false, true})
2009 {
2010 Env env{*this, features};
2011 if (NumberSwitchOver)
2012 env.enableFeature(fixUniversalNumber);
2013 else
2014 env.disableFeature(fixUniversalNumber);
2015
2016 auto const gw = Account{"gateway"};
2017 auto const alice = Account{"alice"};
2018 auto const bob = Account{"bob"};
2019 auto const USD = gw["USD"];
2020
2021 env.fund(XRP(10000), gw, alice, bob);
2022 env.close();
2023
2024 env(rate(gw, 1.005));
2025
2026 env(trust(alice, USD(1000)));
2027 env(trust(bob, USD(1000)));
2028 env(trust(gw, alice["USD"](50)));
2029
2030 env(pay(gw, bob, bob["USD"](1)));
2031 env(pay(alice, gw, USD(50)));
2032
2033 env(trust(gw, alice["USD"](0)));
2034
2035 env(offer(alice, USD(50), XRP(150000)));
2036 env(offer(bob, XRP(100), USD(0.1)));
2037
2038 auto jrr = ledgerEntryState(env, alice, gw, "USD");
2039 BEAST_EXPECT(
2040 jrr[jss::node][sfBalance.fieldName][jss::value] ==
2041 "49.96666666666667");
2042
2043 jrr = ledgerEntryState(env, bob, gw, "USD");
2044 Json::Value const bobsUSD =
2045 jrr[jss::node][sfBalance.fieldName][jss::value];
2046 if (!NumberSwitchOver)
2047 {
2048 BEAST_EXPECT(bobsUSD == "-0.966500000033334");
2049 }
2050 else
2051 {
2052 BEAST_EXPECT(bobsUSD == "-0.9665000000333333");
2053 }
2054 }
2055 }
2056
2057 void
2059 {
2060 testcase("Offer tfSell: Basic Sell");
2061
2062 using namespace jtx;
2063
2064 Env env{*this, features};
2065
2066 auto const gw = Account{"gateway"};
2067 auto const alice = Account{"alice"};
2068 auto const bob = Account{"bob"};
2069 auto const USD = gw["USD"];
2070
2071 auto const starting_xrp = XRP(100) +
2072 env.current()->fees().accountReserve(1) +
2073 env.current()->fees().base * 2;
2074
2075 env.fund(starting_xrp, gw, alice, bob);
2076 env.close();
2077
2078 env(trust(alice, USD(1000)));
2079 env(trust(bob, USD(1000)));
2080
2081 env(pay(gw, bob, bob["USD"](500)));
2082
2083 env(offer(bob, XRP(200), USD(200)), json(jss::Flags, tfSell));
2084 // Alice has 350 + fees - a reserve of 50 = 250 reserve = 100 available.
2085 // Alice has 350 + fees - a reserve of 50 = 250 reserve = 100 available.
2086 // Ask for more than available to prove reserve works.
2087 env(offer(alice, USD(200), XRP(200)), json(jss::Flags, tfSell));
2088
2089 auto jrr = ledgerEntryState(env, alice, gw, "USD");
2090 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-100");
2091 jrr = ledgerEntryRoot(env, alice);
2092 BEAST_EXPECT(
2093 jrr[jss::node][sfBalance.fieldName] ==
2094 STAmount(env.current()->fees().accountReserve(1)).getText());
2095
2096 jrr = ledgerEntryState(env, bob, gw, "USD");
2097 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-400");
2098 }
2099
2100 void
2102 {
2103 testcase("Offer tfSell: 2x Sell Exceed Limit");
2104
2105 using namespace jtx;
2106
2107 Env env{*this, features};
2108
2109 auto const gw = Account{"gateway"};
2110 auto const alice = Account{"alice"};
2111 auto const bob = Account{"bob"};
2112 auto const USD = gw["USD"];
2113
2114 auto const starting_xrp = XRP(100) +
2115 env.current()->fees().accountReserve(1) +
2116 env.current()->fees().base * 2;
2117
2118 env.fund(starting_xrp, gw, alice, bob);
2119 env.close();
2120
2121 env(trust(alice, USD(150)));
2122 env(trust(bob, USD(1000)));
2123
2124 env(pay(gw, bob, bob["USD"](500)));
2125
2126 env(offer(bob, XRP(100), USD(200)));
2127 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
2128 // Ask for more than available to prove reserve works.
2129 // Taker pays 100 USD for 100 XRP.
2130 // Selling XRP.
2131 // Will sell all 100 XRP and get more USD than asked for.
2132 env(offer(alice, USD(100), XRP(100)), json(jss::Flags, tfSell));
2133
2134 auto jrr = ledgerEntryState(env, alice, gw, "USD");
2135 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-200");
2136 jrr = ledgerEntryRoot(env, alice);
2137 BEAST_EXPECT(
2138 jrr[jss::node][sfBalance.fieldName] ==
2139 STAmount(env.current()->fees().accountReserve(1)).getText());
2140
2141 jrr = ledgerEntryState(env, bob, gw, "USD");
2142 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-300");
2143 }
2144
2145 void
2147 {
2148 testcase("Client Issue #535: Gateway Cross Currency");
2149
2150 using namespace jtx;
2151
2152 Env env{*this, features};
2153
2154 auto const gw = Account{"gateway"};
2155 auto const alice = Account{"alice"};
2156 auto const bob = Account{"bob"};
2157 auto const XTS = gw["XTS"];
2158 auto const XXX = gw["XXX"];
2159
2160 auto const starting_xrp = XRP(100.1) +
2161 env.current()->fees().accountReserve(1) +
2162 env.current()->fees().base * 2;
2163
2164 env.fund(starting_xrp, gw, alice, bob);
2165 env.close();
2166
2167 env(trust(alice, XTS(1000)));
2168 env(trust(alice, XXX(1000)));
2169 env(trust(bob, XTS(1000)));
2170 env(trust(bob, XXX(1000)));
2171
2172 env(pay(gw, alice, alice["XTS"](100)));
2173 env(pay(gw, alice, alice["XXX"](100)));
2174 env(pay(gw, bob, bob["XTS"](100)));
2175 env(pay(gw, bob, bob["XXX"](100)));
2176
2177 env(offer(alice, XTS(100), XXX(100)));
2178
2179 // WS client is used here because the RPC client could not
2180 // be convinced to pass the build_path argument
2181 auto wsc = makeWSClient(env.app().config());
2182 Json::Value payment;
2183 payment[jss::secret] = toBase58(generateSeed("bob"));
2184 payment[jss::id] = env.seq(bob);
2185 payment[jss::build_path] = true;
2186 payment[jss::tx_json] = pay(bob, bob, bob["XXX"](1));
2187 payment[jss::tx_json][jss::Sequence] =
2188 env.current()
2189 ->read(keylet::account(bob.id()))
2190 ->getFieldU32(sfSequence);
2191 payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
2192 payment[jss::tx_json][jss::SendMax] =
2193 bob["XTS"](1.5).value().getJson(JsonOptions::none);
2194 auto jrr = wsc->invoke("submit", payment);
2195 BEAST_EXPECT(jrr[jss::status] == "success");
2196 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
2197 if (wsc->version() == 2)
2198 {
2199 BEAST_EXPECT(
2200 jrr.isMember(jss::jsonrpc) && jrr[jss::jsonrpc] == "2.0");
2201 BEAST_EXPECT(
2202 jrr.isMember(jss::ripplerpc) && jrr[jss::ripplerpc] == "2.0");
2203 BEAST_EXPECT(jrr.isMember(jss::id) && jrr[jss::id] == 5);
2204 }
2205
2206 jrr = ledgerEntryState(env, alice, gw, "XTS");
2207 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-101");
2208 jrr = ledgerEntryState(env, alice, gw, "XXX");
2209 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-99");
2210
2211 jrr = ledgerEntryState(env, bob, gw, "XTS");
2212 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-99");
2213 jrr = ledgerEntryState(env, bob, gw, "XXX");
2214 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-101");
2215 }
2216
2217 // Helper function that validates a *defaulted* trustline: one that has
2218 // no unusual flags set and doesn't have high or low limits set. Such a
2219 // trustline may have an actual balance (it can be created automatically
2220 // if a user places an offer to acquire an IOU for which they don't have
2221 // a trust line defined). If the trustline is not defaulted then the tests
2222 // will not pass.
2223 void
2225 jtx::Env& env,
2226 jtx::Account const& account,
2227 jtx::PrettyAmount const& expectBalance)
2228 {
2229 auto const sleTrust =
2230 env.le(keylet::line(account.id(), expectBalance.value().issue()));
2231 BEAST_EXPECT(sleTrust);
2232 if (sleTrust)
2233 {
2234 Issue const issue = expectBalance.value().issue();
2235 bool const accountLow = account.id() < issue.account;
2236
2237 STAmount low{issue};
2238 STAmount high{issue};
2239
2240 low.setIssuer(accountLow ? account.id() : issue.account);
2241 high.setIssuer(accountLow ? issue.account : account.id());
2242
2243 BEAST_EXPECT(sleTrust->getFieldAmount(sfLowLimit) == low);
2244 BEAST_EXPECT(sleTrust->getFieldAmount(sfHighLimit) == high);
2245
2246 STAmount actualBalance{sleTrust->getFieldAmount(sfBalance)};
2247 if (!accountLow)
2248 actualBalance.negate();
2249
2250 BEAST_EXPECT(actualBalance == expectBalance);
2251 }
2252 }
2253
2254 void
2256 {
2257 // Test a number of different corner cases regarding adding a
2258 // possibly crossable offer to an account. The test is table
2259 // driven so it should be easy to add or remove tests.
2260 testcase("Partial Crossing");
2261
2262 using namespace jtx;
2263
2264 auto const gw = Account("gateway");
2265 auto const USD = gw["USD"];
2266
2267 Env env{*this, features};
2268
2269 env.fund(XRP(10000000), gw);
2270 env.close();
2271
2272 // The fee that's charged for transactions
2273 auto const f = env.current()->fees().base;
2274
2275 // To keep things simple all offers are 1 : 1 for XRP : USD.
2276 enum preTrustType { noPreTrust, gwPreTrust, acctPreTrust };
2277 struct TestData
2278 {
2279 std::string account; // Account operated on
2280 STAmount fundXrp; // Account funded with
2281 int bookAmount; // USD -> XRP offer on the books
2282 preTrustType preTrust; // If true, pre-establish trust line
2283 int offerAmount; // Account offers this much XRP -> USD
2284 TER tec; // Returned tec code
2285 STAmount spentXrp; // Amount removed from fundXrp
2286 PrettyAmount balanceUsd; // Balance on account end
2287 int offers; // Offers on account
2288 int owners; // Owners on account
2289 };
2290
2291 // clang-format off
2292 TestData const tests[]{
2293 // acct fundXrp bookAmt preTrust offerAmount tec spentXrp balanceUSD offers owners
2294 {"ann", reserve(env, 0) + 0 * f, 1, noPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0}, // Account is at the reserve, and will dip below once fees are subtracted.
2295 {"bev", reserve(env, 0) + 1 * f, 1, noPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0}, // Account has just enough for the reserve and the fee.
2296 {"cam", reserve(env, 0) + 2 * f, 0, noPreTrust, 1000, tecINSUF_RESERVE_OFFER, f, USD( 0), 0, 0}, // Account has enough for the reserve, the fee and the offer, and a bit more, but not enough for the reserve after the offer is placed.
2297 {"deb", drops(10) + reserve(env, 0) + 1 * f, 1, noPreTrust, 1000, tesSUCCESS, drops(10) + f, USD(0.00001), 0, 1}, // Account has enough to buy a little USD then the offer runs dry.
2298 {"eve", reserve(env, 1) + 0 * f, 0, noPreTrust, 1000, tesSUCCESS, f, USD( 0), 1, 1}, // No offer to cross
2299 {"flo", reserve(env, 1) + 0 * f, 1, noPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 0, 1},
2300 {"gay", reserve(env, 1) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP( 50) + f, USD( 50), 0, 1},
2301 {"hye", XRP(1000) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP( 800) + f, USD( 800), 0, 1},
2302 {"ivy", XRP( 1) + reserve(env, 1) + 1 * f, 1, noPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 0, 1},
2303 {"joy", XRP( 1) + reserve(env, 2) + 1 * f, 1, noPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 1, 2},
2304 {"kim", XRP( 900) + reserve(env, 2) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 0, 1},
2305 {"liz", XRP( 998) + reserve(env, 0) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 998) + f, USD( 998), 0, 1},
2306 {"meg", XRP( 998) + reserve(env, 1) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 0, 1},
2307 {"nia", XRP( 998) + reserve(env, 2) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 1, 2},
2308 {"ova", XRP( 999) + reserve(env, 0) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 0, 1},
2309 {"pam", XRP( 999) + reserve(env, 1) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP(1000) + f, USD( 1000), 0, 1},
2310 {"rae", XRP( 999) + reserve(env, 2) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP(1000) + f, USD( 1000), 0, 1},
2311 {"sue", XRP(1000) + reserve(env, 2) + 1 * f, 0, noPreTrust, 1000, tesSUCCESS, f, USD( 0), 1, 1},
2312 //---------------- Pre-established trust lines ---------------------
2313 {"abe", reserve(env, 0) + 0 * f, 1, gwPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0},
2314 {"bud", reserve(env, 0) + 1 * f, 1, gwPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0},
2315 {"che", reserve(env, 0) + 2 * f, 0, gwPreTrust, 1000, tecINSUF_RESERVE_OFFER, f, USD( 0), 0, 0},
2316 {"dan", drops(10) + reserve(env, 0) + 1 * f, 1, gwPreTrust, 1000, tesSUCCESS, drops(10) + f, USD(0.00001), 0, 0},
2317 {"eli", XRP( 20) + reserve(env, 0) + 1 * f, 1000, gwPreTrust, 1000, tesSUCCESS, XRP(20) + 1 * f, USD( 20), 0, 0},
2318 {"fyn", reserve(env, 1) + 0 * f, 0, gwPreTrust, 1000, tesSUCCESS, f, USD( 0), 1, 1},
2319 {"gar", reserve(env, 1) + 0 * f, 1, gwPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 1, 1},
2320 {"hal", reserve(env, 1) + 1 * f, 1, gwPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 1, 1},
2321
2322 {"ned", reserve(env, 1) + 0 * f, 1, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1},
2323 {"ole", reserve(env, 1) + 1 * f, 1, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1},
2324 {"pat", reserve(env, 1) + 2 * f, 0, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1},
2325 {"quy", reserve(env, 1) + 2 * f, 1, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1},
2326 {"ron", reserve(env, 1) + 3 * f, 0, acctPreTrust, 1000, tecINSUF_RESERVE_OFFER, 2 * f, USD( 0), 0, 1},
2327 {"syd", drops(10) + reserve(env, 1) + 2 * f, 1, acctPreTrust, 1000, tesSUCCESS, drops(10) + 2 * f, USD(0.00001), 0, 1},
2328 {"ted", XRP( 20) + reserve(env, 1) + 2 * f, 1000, acctPreTrust, 1000, tesSUCCESS, XRP(20) + 2 * f, USD( 20), 0, 1},
2329 {"uli", reserve(env, 2) + 0 * f, 0, acctPreTrust, 1000, tecINSUF_RESERVE_OFFER, 2 * f, USD( 0), 0, 1},
2330 {"vic", reserve(env, 2) + 0 * f, 1, acctPreTrust, 1000, tesSUCCESS, XRP( 1) + 2 * f, USD( 1), 0, 1},
2331 {"wes", reserve(env, 2) + 1 * f, 0, acctPreTrust, 1000, tesSUCCESS, 2 * f, USD( 0), 1, 2},
2332 {"xan", reserve(env, 2) + 1 * f, 1, acctPreTrust, 1000, tesSUCCESS, XRP( 1) + 2 * f, USD( 1), 1, 2},
2333 };
2334 // clang-format on
2335
2336 for (auto const& t : tests)
2337 {
2338 auto const acct = Account(t.account);
2339 env.fund(t.fundXrp, acct);
2340 env.close();
2341
2342 // Make sure gateway has no current offers.
2343 env.require(offers(gw, 0));
2344
2345 // The gateway optionally creates an offer that would be crossed.
2346 auto const book = t.bookAmount;
2347 if (book)
2348 env(offer(gw, XRP(book), USD(book)));
2349 env.close();
2350 std::uint32_t const gwOfferSeq = env.seq(gw) - 1;
2351
2352 // Optionally pre-establish a trustline between gw and acct.
2353 if (t.preTrust == gwPreTrust)
2354 env(trust(gw, acct["USD"](1)));
2355 env.close();
2356
2357 // Optionally pre-establish a trustline between acct and gw.
2358 // Note this is not really part of the test, so we expect there
2359 // to be enough XRP reserve for acct to create the trust line.
2360 if (t.preTrust == acctPreTrust)
2361 env(trust(acct, USD(1)));
2362 env.close();
2363
2364 {
2365 // Acct creates an offer. This is the heart of the test.
2366 auto const acctOffer = t.offerAmount;
2367 env(offer(acct, USD(acctOffer), XRP(acctOffer)), ter(t.tec));
2368 env.close();
2369 }
2370 std::uint32_t const acctOfferSeq = env.seq(acct) - 1;
2371
2372 BEAST_EXPECT(env.balance(acct, USD.issue()) == t.balanceUsd);
2373 BEAST_EXPECT(
2374 env.balance(acct, xrpIssue()) == t.fundXrp - t.spentXrp);
2375 env.require(offers(acct, t.offers));
2376 env.require(owners(acct, t.owners));
2377
2378 auto acctOffers = offersOnAccount(env, acct);
2379 BEAST_EXPECT(acctOffers.size() == t.offers);
2380 if (acctOffers.size() && t.offers)
2381 {
2382 auto const& acctOffer = *(acctOffers.front());
2383
2384 auto const leftover = t.offerAmount - t.bookAmount;
2385 BEAST_EXPECT(acctOffer[sfTakerGets] == XRP(leftover));
2386 BEAST_EXPECT(acctOffer[sfTakerPays] == USD(leftover));
2387 }
2388
2389 if (t.preTrust == noPreTrust)
2390 {
2391 if (t.balanceUsd.value().signum())
2392 {
2393 // Verify the correct contents of the trustline
2394 verifyDefaultTrustline(env, acct, t.balanceUsd);
2395 }
2396 else
2397 {
2398 // Verify that no trustline was created.
2399 auto const sleTrust =
2400 env.le(keylet::line(acct, USD.issue()));
2401 BEAST_EXPECT(!sleTrust);
2402 }
2403 }
2404
2405 // Give the next loop a clean slate by canceling any left-overs
2406 // in the offers.
2407 env(offer_cancel(acct, acctOfferSeq));
2408 env(offer_cancel(gw, gwOfferSeq));
2409 env.close();
2410 }
2411 }
2412
2413 void
2415 {
2416 testcase("XRP Direct Crossing");
2417
2418 using namespace jtx;
2419
2420 auto const gw = Account("gateway");
2421 auto const alice = Account("alice");
2422 auto const bob = Account("bob");
2423 auto const USD = gw["USD"];
2424
2425 auto const usdOffer = USD(1000);
2426 auto const xrpOffer = XRP(1000);
2427
2428 Env env{*this, features};
2429
2430 env.fund(XRP(1000000), gw, bob);
2431 env.close();
2432
2433 // The fee that's charged for transactions.
2434 auto const fee = env.current()->fees().base;
2435
2436 // alice's account has enough for the reserve, one trust line plus two
2437 // offers, and two fees.
2438 env.fund(reserve(env, 2) + fee * 2, alice);
2439 env.close();
2440
2441 env(trust(alice, usdOffer));
2442
2443 env.close();
2444
2445 env(pay(gw, alice, usdOffer));
2446 env.close();
2447 env.require(balance(alice, usdOffer), offers(alice, 0), offers(bob, 0));
2448
2449 // The scenario:
2450 // o alice has USD but wants XRP.
2451 // o bob has XRP but wants USD.
2452 auto const alicesXRP = env.balance(alice);
2453 auto const bobsXRP = env.balance(bob);
2454
2455 env(offer(alice, xrpOffer, usdOffer));
2456 env.close();
2457 env(offer(bob, usdOffer, xrpOffer));
2458
2459 env.close();
2460 env.require(
2461 balance(alice, USD(0)),
2462 balance(bob, usdOffer),
2463 balance(alice, alicesXRP + xrpOffer - fee),
2464 balance(bob, bobsXRP - xrpOffer - fee),
2465 offers(alice, 0),
2466 offers(bob, 0));
2467
2468 verifyDefaultTrustline(env, bob, usdOffer);
2469
2470 // Make two more offers that leave one of the offers non-dry.
2471 env(offer(alice, USD(999), XRP(999)));
2472 env(offer(bob, xrpOffer, usdOffer));
2473
2474 env.close();
2475 env.require(balance(alice, USD(999)));
2476 env.require(balance(bob, USD(1)));
2477 env.require(offers(alice, 0));
2478 verifyDefaultTrustline(env, bob, USD(1));
2479 {
2480 auto const bobsOffers = offersOnAccount(env, bob);
2481 BEAST_EXPECT(bobsOffers.size() == 1);
2482 auto const& bobsOffer = *(bobsOffers.front());
2483
2484 BEAST_EXPECT(bobsOffer[sfLedgerEntryType] == ltOFFER);
2485 BEAST_EXPECT(bobsOffer[sfTakerGets] == USD(1));
2486 BEAST_EXPECT(bobsOffer[sfTakerPays] == XRP(1));
2487 }
2488 }
2489
2490 void
2492 {
2493 testcase("Direct Crossing");
2494
2495 using namespace jtx;
2496
2497 auto const gw = Account("gateway");
2498 auto const alice = Account("alice");
2499 auto const bob = Account("bob");
2500 auto const USD = gw["USD"];
2501 auto const EUR = gw["EUR"];
2502
2503 auto const usdOffer = USD(1000);
2504 auto const eurOffer = EUR(1000);
2505
2506 Env env{*this, features};
2507
2508 env.fund(XRP(1000000), gw);
2509 env.close();
2510
2511 // The fee that's charged for transactions.
2512 auto const fee = env.current()->fees().base;
2513
2514 // Each account has enough for the reserve, two trust lines, one
2515 // offer, and two fees.
2516 env.fund(reserve(env, 3) + fee * 3, alice);
2517 env.fund(reserve(env, 3) + fee * 2, bob);
2518 env.close();
2519
2520 env(trust(alice, usdOffer));
2521 env(trust(bob, eurOffer));
2522 env.close();
2523
2524 env(pay(gw, alice, usdOffer));
2525 env(pay(gw, bob, eurOffer));
2526 env.close();
2527
2528 env.require(balance(alice, usdOffer), balance(bob, eurOffer));
2529
2530 // The scenario:
2531 // o alice has USD but wants EUR.
2532 // o bob has EUR but wants USD.
2533 env(offer(alice, eurOffer, usdOffer));
2534 env(offer(bob, usdOffer, eurOffer));
2535
2536 env.close();
2537 env.require(
2538 balance(alice, eurOffer),
2539 balance(bob, usdOffer),
2540 offers(alice, 0),
2541 offers(bob, 0));
2542
2543 // Alice's offer crossing created a default EUR trustline and
2544 // Bob's offer crossing created a default USD trustline:
2545 verifyDefaultTrustline(env, alice, eurOffer);
2546 verifyDefaultTrustline(env, bob, usdOffer);
2547
2548 // Make two more offers that leave one of the offers non-dry.
2549 // Guarantee the order of application by putting a close()
2550 // between them.
2551 env(offer(bob, eurOffer, usdOffer));
2552 env.close();
2553
2554 env(offer(alice, USD(999), eurOffer));
2555 env.close();
2556
2557 env.require(offers(alice, 0));
2558 env.require(offers(bob, 1));
2559
2560 env.require(balance(alice, USD(999)));
2561 env.require(balance(alice, EUR(1)));
2562 env.require(balance(bob, USD(1)));
2563 env.require(balance(bob, EUR(999)));
2564
2565 {
2566 auto bobsOffers = offersOnAccount(env, bob);
2567 if (BEAST_EXPECT(bobsOffers.size() == 1))
2568 {
2569 auto const& bobsOffer = *(bobsOffers.front());
2570
2571 BEAST_EXPECT(bobsOffer[sfTakerGets] == USD(1));
2572 BEAST_EXPECT(bobsOffer[sfTakerPays] == EUR(1));
2573 }
2574 }
2575
2576 // alice makes one more offer that cleans out bob's offer.
2577 env(offer(alice, USD(1), EUR(1)));
2578 env.close();
2579
2580 env.require(balance(alice, USD(1000)));
2581 env.require(balance(alice, EUR(none)));
2582 env.require(balance(bob, USD(none)));
2583 env.require(balance(bob, EUR(1000)));
2584 env.require(offers(alice, 0));
2585 env.require(offers(bob, 0));
2586
2587 // The two trustlines that were generated by offers should be gone.
2588 BEAST_EXPECT(!env.le(keylet::line(alice.id(), EUR.issue())));
2589 BEAST_EXPECT(!env.le(keylet::line(bob.id(), USD.issue())));
2590
2591 // Make two more offers that leave one of the offers non-dry. We
2592 // need to properly sequence the transactions:
2593 env(offer(alice, EUR(999), usdOffer));
2594 env.close();
2595
2596 env(offer(bob, usdOffer, eurOffer));
2597 env.close();
2598
2599 env.require(offers(alice, 0));
2600 env.require(offers(bob, 0));
2601
2602 env.require(balance(alice, USD(0)));
2603 env.require(balance(alice, EUR(999)));
2604 env.require(balance(bob, USD(1000)));
2605 env.require(balance(bob, EUR(1)));
2606 }
2607
2608 void
2610 {
2611 testcase("Bridged Crossing");
2612
2613 using namespace jtx;
2614
2615 auto const gw = Account("gateway");
2616 auto const alice = Account("alice");
2617 auto const bob = Account("bob");
2618 auto const carol = Account("carol");
2619 auto const USD = gw["USD"];
2620 auto const EUR = gw["EUR"];
2621
2622 auto const usdOffer = USD(1000);
2623 auto const eurOffer = EUR(1000);
2624
2625 Env env{*this, features};
2626
2627 env.fund(XRP(1000000), gw, alice, bob, carol);
2628 env.close();
2629
2630 env(trust(alice, usdOffer));
2631 env(trust(carol, eurOffer));
2632 env.close();
2633 env(pay(gw, alice, usdOffer));
2634 env(pay(gw, carol, eurOffer));
2635 env.close();
2636
2637 // The scenario:
2638 // o alice has USD but wants XRP.
2639 // o bob has XRP but wants EUR.
2640 // o carol has EUR but wants USD.
2641 // Note that carol's offer must come last. If carol's offer is placed
2642 // before bob's or alice's, then autobridging will not occur.
2643 env(offer(alice, XRP(1000), usdOffer));
2644 env(offer(bob, eurOffer, XRP(1000)));
2645 auto const bobXrpBalance = env.balance(bob);
2646 env.close();
2647
2648 // carol makes an offer that partially consumes alice and bob's offers.
2649 env(offer(carol, USD(400), EUR(400)));
2650 env.close();
2651
2652 env.require(
2653 balance(alice, USD(600)),
2654 balance(bob, EUR(400)),
2655 balance(carol, USD(400)),
2656 balance(bob, bobXrpBalance - XRP(400)),
2657 offers(carol, 0));
2658 verifyDefaultTrustline(env, bob, EUR(400));
2659 verifyDefaultTrustline(env, carol, USD(400));
2660 {
2661 auto const alicesOffers = offersOnAccount(env, alice);
2662 BEAST_EXPECT(alicesOffers.size() == 1);
2663 auto const& alicesOffer = *(alicesOffers.front());
2664
2665 BEAST_EXPECT(alicesOffer[sfLedgerEntryType] == ltOFFER);
2666 BEAST_EXPECT(alicesOffer[sfTakerGets] == USD(600));
2667 BEAST_EXPECT(alicesOffer[sfTakerPays] == XRP(600));
2668 }
2669 {
2670 auto const bobsOffers = offersOnAccount(env, bob);
2671 BEAST_EXPECT(bobsOffers.size() == 1);
2672 auto const& bobsOffer = *(bobsOffers.front());
2673
2674 BEAST_EXPECT(bobsOffer[sfLedgerEntryType] == ltOFFER);
2675 BEAST_EXPECT(bobsOffer[sfTakerGets] == XRP(600));
2676 BEAST_EXPECT(bobsOffer[sfTakerPays] == EUR(600));
2677 }
2678
2679 // carol makes an offer that exactly consumes alice and bob's offers.
2680 env(offer(carol, USD(600), EUR(600)));
2681 env.close();
2682
2683 env.require(
2684 balance(alice, USD(0)),
2685 balance(bob, eurOffer),
2686 balance(carol, usdOffer),
2687 balance(bob, bobXrpBalance - XRP(1000)),
2688 offers(bob, 0),
2689 offers(carol, 0));
2690 verifyDefaultTrustline(env, bob, EUR(1000));
2691 verifyDefaultTrustline(env, carol, USD(1000));
2692
2693 // In pre-flow code alice's offer is left empty in the ledger.
2694 auto const alicesOffers = offersOnAccount(env, alice);
2695 if (alicesOffers.size() != 0)
2696 {
2697 BEAST_EXPECT(alicesOffers.size() == 1);
2698 auto const& alicesOffer = *(alicesOffers.front());
2699
2700 BEAST_EXPECT(alicesOffer[sfLedgerEntryType] == ltOFFER);
2701 BEAST_EXPECT(alicesOffer[sfTakerGets] == USD(0));
2702 BEAST_EXPECT(alicesOffer[sfTakerPays] == XRP(0));
2703 }
2704 }
2705
2706 void
2708 {
2709 // Test a number of different corner cases regarding offer crossing
2710 // when the tfSell flag is set. The test is table driven so it
2711 // should be easy to add or remove tests.
2712 testcase("Sell Offer");
2713
2714 using namespace jtx;
2715
2716 auto const gw = Account("gateway");
2717 auto const USD = gw["USD"];
2718
2719 Env env{*this, features};
2720
2721 env.fund(XRP(10000000), gw);
2722 env.close();
2723
2724 // The fee that's charged for transactions
2725 auto const f = env.current()->fees().base;
2726
2727 // To keep things simple all offers are 1 : 1 for XRP : USD.
2728 enum preTrustType { noPreTrust, gwPreTrust, acctPreTrust };
2729 struct TestData
2730 {
2731 std::string account; // Account operated on
2732 STAmount fundXrp; // XRP acct funded with
2733 STAmount fundUSD; // USD acct funded with
2734 STAmount gwGets; // gw's offer
2735 STAmount gwPays; //
2736 STAmount acctGets; // acct's offer
2737 STAmount acctPays; //
2738 TER tec; // Returned tec code
2739 STAmount spentXrp; // Amount removed from fundXrp
2740 STAmount finalUsd; // Final USD balance on acct
2741 int offers; // Offers on acct
2742 int owners; // Owners on acct
2743 STAmount takerGets; // Remainder of acct's offer
2744 STAmount takerPays; //
2745
2746 // Constructor with takerGets/takerPays
2747 TestData(
2748 std::string&& account_, // Account operated on
2749 STAmount const& fundXrp_, // XRP acct funded with
2750 STAmount const& fundUSD_, // USD acct funded with
2751 STAmount const& gwGets_, // gw's offer
2752 STAmount const& gwPays_, //
2753 STAmount const& acctGets_, // acct's offer
2754 STAmount const& acctPays_, //
2755 TER tec_, // Returned tec code
2756 STAmount const& spentXrp_, // Amount removed from fundXrp
2757 STAmount const& finalUsd_, // Final USD balance on acct
2758 int offers_, // Offers on acct
2759 int owners_, // Owners on acct
2760 STAmount const& takerGets_, // Remainder of acct's offer
2761 STAmount const& takerPays_) //
2762 : account(std::move(account_))
2763 , fundXrp(fundXrp_)
2764 , fundUSD(fundUSD_)
2765 , gwGets(gwGets_)
2766 , gwPays(gwPays_)
2767 , acctGets(acctGets_)
2768 , acctPays(acctPays_)
2769 , tec(tec_)
2770 , spentXrp(spentXrp_)
2771 , finalUsd(finalUsd_)
2772 , offers(offers_)
2773 , owners(owners_)
2774 , takerGets(takerGets_)
2775 , takerPays(takerPays_)
2776 {
2777 }
2778
2779 // Constructor without takerGets/takerPays
2780 TestData(
2781 std::string&& account_, // Account operated on
2782 STAmount const& fundXrp_, // XRP acct funded with
2783 STAmount const& fundUSD_, // USD acct funded with
2784 STAmount const& gwGets_, // gw's offer
2785 STAmount const& gwPays_, //
2786 STAmount const& acctGets_, // acct's offer
2787 STAmount const& acctPays_, //
2788 TER tec_, // Returned tec code
2789 STAmount const& spentXrp_, // Amount removed from fundXrp
2790 STAmount const& finalUsd_, // Final USD balance on acct
2791 int offers_, // Offers on acct
2792 int owners_) // Owners on acct
2793 : TestData(
2794 std::move(account_),
2795 fundXrp_,
2796 fundUSD_,
2797 gwGets_,
2798 gwPays_,
2799 acctGets_,
2800 acctPays_,
2801 tec_,
2802 spentXrp_,
2803 finalUsd_,
2804 offers_,
2805 owners_,
2806 STAmount{0},
2807 STAmount{0})
2808 {
2809 }
2810 };
2811
2812 // clang-format off
2813 TestData const tests[]{
2814 // acct pays XRP
2815 // acct fundXrp fundUSD gwGets gwPays acctGets acctPays tec spentXrp finalUSD offers owners takerGets takerPays
2816 {"ann", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD( 5), USD(10), XRP(10), tecINSUF_RESERVE_OFFER, XRP( 0) + (1 * f), USD( 0), 0, 0},
2817 {"bev", XRP(10) + reserve(env, 1) + 1 * f, USD( 0), XRP(10), USD( 5), USD(10), XRP(10), tesSUCCESS, XRP( 0) + (1 * f), USD( 0), 1, 1, XRP(10), USD(10)},
2818 {"cam", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(10), USD(10), XRP(10), tesSUCCESS, XRP( 10) + (1 * f), USD(10), 0, 1},
2819 {"deb", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(20), USD(10), XRP(10), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 0, 1},
2820 {"eve", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(20), USD( 5), XRP( 5), tesSUCCESS, XRP( 5) + (1 * f), USD(10), 0, 1},
2821 {"flo", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(20), USD(20), XRP(20), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 0, 1},
2822 {"gay", XRP(20) + reserve(env, 1) + 1 * f, USD( 0), XRP(10), USD(20), USD(20), XRP(20), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 0, 1},
2823 {"hye", XRP(20) + reserve(env, 2) + 1 * f, USD( 0), XRP(10), USD(20), USD(20), XRP(20), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 1, 2, XRP(10), USD(10)},
2824 // acct pays USD
2825 {"meg", reserve(env, 1) + 2 * f, USD(10), USD(10), XRP( 5), XRP(10), USD(10), tecINSUF_RESERVE_OFFER, XRP( 0) + (2 * f), USD(10), 0, 1},
2826 {"nia", reserve(env, 2) + 2 * f, USD(10), USD(10), XRP( 5), XRP(10), USD(10), tesSUCCESS, XRP( 0) + (2 * f), USD(10), 1, 2, USD(10), XRP(10)},
2827 {"ova", reserve(env, 1) + 2 * f, USD(10), USD(10), XRP(10), XRP(10), USD(10), tesSUCCESS, XRP(-10) + (2 * f), USD( 0), 0, 1},
2828 {"pam", reserve(env, 1) + 2 * f, USD(10), USD(10), XRP(20), XRP(10), USD(10), tesSUCCESS, XRP(-20) + (2 * f), USD( 0), 0, 1},
2829 {"qui", reserve(env, 1) + 2 * f, USD(10), USD(20), XRP(40), XRP(10), USD(10), tesSUCCESS, XRP(-20) + (2 * f), USD( 0), 0, 1},
2830 {"rae", reserve(env, 2) + 2 * f, USD(10), USD( 5), XRP( 5), XRP(10), USD(10), tesSUCCESS, XRP( -5) + (2 * f), USD( 5), 1, 2, USD( 5), XRP( 5)},
2831 {"sue", reserve(env, 2) + 2 * f, USD(10), USD( 5), XRP(10), XRP(10), USD(10), tesSUCCESS, XRP(-10) + (2 * f), USD( 5), 1, 2, USD( 5), XRP( 5)},
2832 };
2833 // clang-format on
2834
2835 auto const zeroUsd = USD(0);
2836 for (auto const& t : tests)
2837 {
2838 // Make sure gateway has no current offers.
2839 env.require(offers(gw, 0));
2840
2841 auto const acct = Account(t.account);
2842
2843 env.fund(t.fundXrp, acct);
2844 env.close();
2845
2846 // Optionally give acct some USD. This is not part of the test,
2847 // so we assume that acct has sufficient USD to cover the reserve
2848 // on the trust line.
2849 if (t.fundUSD != zeroUsd)
2850 {
2851 env(trust(acct, t.fundUSD));
2852 env.close();
2853 env(pay(gw, acct, t.fundUSD));
2854 env.close();
2855 }
2856
2857 env(offer(gw, t.gwGets, t.gwPays));
2858 env.close();
2859 std::uint32_t const gwOfferSeq = env.seq(gw) - 1;
2860
2861 // Acct creates a tfSell offer. This is the heart of the test.
2862 env(offer(acct, t.acctGets, t.acctPays, tfSell), ter(t.tec));
2863 env.close();
2864 std::uint32_t const acctOfferSeq = env.seq(acct) - 1;
2865
2866 // Check results
2867 BEAST_EXPECT(env.balance(acct, USD.issue()) == t.finalUsd);
2868 BEAST_EXPECT(
2869 env.balance(acct, xrpIssue()) == t.fundXrp - t.spentXrp);
2870 env.require(offers(acct, t.offers));
2871 env.require(owners(acct, t.owners));
2872
2873 if (t.offers)
2874 {
2875 auto const acctOffers = offersOnAccount(env, acct);
2876 if (acctOffers.size() > 0)
2877 {
2878 BEAST_EXPECT(acctOffers.size() == 1);
2879 auto const& acctOffer = *(acctOffers.front());
2880
2881 BEAST_EXPECT(acctOffer[sfLedgerEntryType] == ltOFFER);
2882 BEAST_EXPECT(acctOffer[sfTakerGets] == t.takerGets);
2883 BEAST_EXPECT(acctOffer[sfTakerPays] == t.takerPays);
2884 }
2885 }
2886
2887 // Give the next loop a clean slate by canceling any left-overs
2888 // in the offers.
2889 env(offer_cancel(acct, acctOfferSeq));
2890 env(offer_cancel(gw, gwOfferSeq));
2891 env.close();
2892 }
2893 }
2894
2895 void
2897 {
2898 // Test a number of different corner cases regarding offer crossing
2899 // when both the tfSell flag and tfFillOrKill flags are set.
2900 testcase("Combine tfSell with tfFillOrKill");
2901
2902 using namespace jtx;
2903
2904 auto const gw = Account("gateway");
2905 auto const alice = Account("alice");
2906 auto const bob = Account("bob");
2907 auto const USD = gw["USD"];
2908
2909 Env env{*this, features};
2910
2911 env.fund(XRP(10000000), gw, alice, bob);
2912 env.close();
2913
2914 // Code returned if an offer is killed.
2915 TER const killedCode{TER{tecKILLED}};
2916
2917 // bob offers XRP for USD.
2918 env(trust(bob, USD(200)));
2919 env.close();
2920 env(pay(gw, bob, USD(100)));
2921 env.close();
2922 env(offer(bob, XRP(2000), USD(20)));
2923 env.close();
2924 {
2925 // alice submits a tfSell | tfFillOrKill offer that does not cross.
2926 env(offer(alice, USD(21), XRP(2100), tfSell | tfFillOrKill),
2927 ter(killedCode));
2928 env.close();
2929 env.require(balance(alice, USD(none)));
2930 env.require(offers(alice, 0));
2931 env.require(balance(bob, USD(100)));
2932 }
2933 {
2934 // alice submits a tfSell | tfFillOrKill offer that crosses.
2935 // Even though tfSell is present it doesn't matter this time.
2936 env(offer(alice, USD(20), XRP(2000), tfSell | tfFillOrKill));
2937 env.close();
2938 env.require(balance(alice, USD(20)));
2939 env.require(offers(alice, 0));
2940 env.require(balance(bob, USD(80)));
2941 }
2942 {
2943 // alice submits a tfSell | tfFillOrKill offer that crosses and
2944 // returns more than was asked for (because of the tfSell flag).
2945 env(offer(bob, XRP(2000), USD(20)));
2946 env.close();
2947 env(offer(alice, USD(10), XRP(1500), tfSell | tfFillOrKill));
2948 env.close();
2949 env.require(balance(alice, USD(35)));
2950 env.require(offers(alice, 0));
2951 env.require(balance(bob, USD(65)));
2952 }
2953 {
2954 // alice submits a tfSell | tfFillOrKill offer that doesn't cross.
2955 // This would have succeeded with a regular tfSell, but the
2956 // fillOrKill prevents the transaction from crossing since not
2957 // all of the offer is consumed.
2958
2959 // We're using bob's left-over offer for XRP(500), USD(5)
2960 env(offer(alice, USD(1), XRP(501), tfSell | tfFillOrKill),
2961 ter(killedCode));
2962 env.close();
2963 env.require(balance(alice, USD(35)));
2964 env.require(offers(alice, 0));
2965 env.require(balance(bob, USD(65)));
2966 }
2967 {
2968 // Alice submits a tfSell | tfFillOrKill offer that finishes
2969 // off the remainder of bob's offer.
2970
2971 // We're using bob's left-over offer for XRP(500), USD(5)
2972 env(offer(alice, USD(1), XRP(500), tfSell | tfFillOrKill));
2973 env.close();
2974 env.require(balance(alice, USD(40)));
2975 env.require(offers(alice, 0));
2976 env.require(balance(bob, USD(60)));
2977 }
2978 }
2979
2980 void
2982 {
2983 testcase("Transfer Rate Offer");
2984
2985 using namespace jtx;
2986
2987 auto const gw1 = Account("gateway1");
2988 auto const USD = gw1["USD"];
2989
2990 Env env{*this, features};
2991
2992 // The fee that's charged for transactions.
2993 auto const fee = env.current()->fees().base;
2994
2995 env.fund(XRP(100000), gw1);
2996 env.close();
2997
2998 env(rate(gw1, 1.25));
2999 {
3000 auto const ann = Account("ann");
3001 auto const bob = Account("bob");
3002 env.fund(XRP(100) + reserve(env, 2) + (fee * 2), ann, bob);
3003 env.close();
3004
3005 env(trust(ann, USD(200)));
3006 env(trust(bob, USD(200)));
3007 env.close();
3008
3009 env(pay(gw1, bob, USD(125)));
3010 env.close();
3011
3012 // bob offers to sell USD(100) for XRP. alice takes bob's offer.
3013 // Notice that although bob only offered USD(100), USD(125) was
3014 // removed from his account due to the gateway fee.
3015 //
3016 // A comparable payment would look like this:
3017 // env (pay (bob, alice, USD(100)), sendmax(USD(125)))
3018 env(offer(bob, XRP(1), USD(100)));
3019 env.close();
3020
3021 env(offer(ann, USD(100), XRP(1)));
3022 env.close();
3023
3024 env.require(balance(ann, USD(100)));
3025 env.require(balance(ann, XRP(99) + reserve(env, 2)));
3026 env.require(offers(ann, 0));
3027
3028 env.require(balance(bob, USD(0)));
3029 env.require(balance(bob, XRP(101) + reserve(env, 2)));
3030 env.require(offers(bob, 0));
3031 }
3032 {
3033 // Reverse the order, so the offer in the books is to sell XRP
3034 // in return for USD. Gateway rate should still apply identically.
3035 auto const che = Account("che");
3036 auto const deb = Account("deb");
3037 env.fund(XRP(100) + reserve(env, 2) + (fee * 2), che, deb);
3038 env.close();
3039
3040 env(trust(che, USD(200)));
3041 env(trust(deb, USD(200)));
3042 env.close();
3043
3044 env(pay(gw1, deb, USD(125)));
3045 env.close();
3046
3047 env(offer(che, USD(100), XRP(1)));
3048 env.close();
3049
3050 env(offer(deb, XRP(1), USD(100)));
3051 env.close();
3052
3053 env.require(balance(che, USD(100)));
3054 env.require(balance(che, XRP(99) + reserve(env, 2)));
3055 env.require(offers(che, 0));
3056
3057 env.require(balance(deb, USD(0)));
3058 env.require(balance(deb, XRP(101) + reserve(env, 2)));
3059 env.require(offers(deb, 0));
3060 }
3061 {
3062 auto const eve = Account("eve");
3063 auto const fyn = Account("fyn");
3064
3065 env.fund(XRP(20000) + (fee * 2), eve, fyn);
3066 env.close();
3067
3068 env(trust(eve, USD(1000)));
3069 env(trust(fyn, USD(1000)));
3070 env.close();
3071
3072 env(pay(gw1, eve, USD(100)));
3073 env(pay(gw1, fyn, USD(100)));
3074 env.close();
3075
3076 // This test verifies that the amount removed from an offer
3077 // accounts for the transfer fee that is removed from the
3078 // account but not from the remaining offer.
3079 env(offer(eve, USD(10), XRP(4000)));
3080 env.close();
3081 std::uint32_t const eveOfferSeq = env.seq(eve) - 1;
3082
3083 env(offer(fyn, XRP(2000), USD(5)));
3084 env.close();
3085
3086 env.require(balance(eve, USD(105)));
3087 env.require(balance(eve, XRP(18000)));
3088 auto const evesOffers = offersOnAccount(env, eve);
3089 BEAST_EXPECT(evesOffers.size() == 1);
3090 if (evesOffers.size() != 0)
3091 {
3092 auto const& evesOffer = *(evesOffers.front());
3093 BEAST_EXPECT(evesOffer[sfLedgerEntryType] == ltOFFER);
3094 BEAST_EXPECT(evesOffer[sfTakerGets] == XRP(2000));
3095 BEAST_EXPECT(evesOffer[sfTakerPays] == USD(5));
3096 }
3097 env(offer_cancel(eve, eveOfferSeq)); // For later tests
3098
3099 env.require(balance(fyn, USD(93.75)));
3100 env.require(balance(fyn, XRP(22000)));
3101 env.require(offers(fyn, 0));
3102 }
3103 // Start messing with two non-native currencies.
3104 auto const gw2 = Account("gateway2");
3105 auto const EUR = gw2["EUR"];
3106
3107 env.fund(XRP(100000), gw2);
3108 env.close();
3109
3110 env(rate(gw2, 1.5));
3111 {
3112 // Remove XRP from the equation. Give the two currencies two
3113 // different transfer rates so we can see both transfer rates
3114 // apply in the same transaction.
3115 auto const gay = Account("gay");
3116 auto const hal = Account("hal");
3117 env.fund(reserve(env, 3) + (fee * 3), gay, hal);
3118 env.close();
3119
3120 env(trust(gay, USD(200)));
3121 env(trust(gay, EUR(200)));
3122 env(trust(hal, USD(200)));
3123 env(trust(hal, EUR(200)));
3124 env.close();
3125
3126 env(pay(gw1, gay, USD(125)));
3127 env(pay(gw2, hal, EUR(150)));
3128 env.close();
3129
3130 env(offer(gay, EUR(100), USD(100)));
3131 env.close();
3132
3133 env(offer(hal, USD(100), EUR(100)));
3134 env.close();
3135
3136 env.require(balance(gay, USD(0)));
3137 env.require(balance(gay, EUR(100)));
3138 env.require(balance(gay, reserve(env, 3)));
3139 env.require(offers(gay, 0));
3140
3141 env.require(balance(hal, USD(100)));
3142 env.require(balance(hal, EUR(0)));
3143 env.require(balance(hal, reserve(env, 3)));
3144 env.require(offers(hal, 0));
3145 }
3146 {
3147 // A trust line's QualityIn should not affect offer crossing.
3148 auto const ivy = Account("ivy");
3149 auto const joe = Account("joe");
3150 env.fund(reserve(env, 3) + (fee * 3), ivy, joe);
3151 env.close();
3152
3153 env(trust(ivy, USD(400)), qualityInPercent(90));
3154 env(trust(ivy, EUR(400)), qualityInPercent(80));
3155 env(trust(joe, USD(400)), qualityInPercent(70));
3156 env(trust(joe, EUR(400)), qualityInPercent(60));
3157 env.close();
3158
3159 env(pay(gw1, ivy, USD(270)), sendmax(USD(500)));
3160 env(pay(gw2, joe, EUR(150)), sendmax(EUR(300)));
3161 env.close();
3162 env.require(balance(ivy, USD(300)));
3163 env.require(balance(joe, EUR(250)));
3164
3165 env(offer(ivy, EUR(100), USD(200)));
3166 env.close();
3167
3168 env(offer(joe, USD(200), EUR(100)));
3169 env.close();
3170
3171 env.require(balance(ivy, USD(50)));
3172 env.require(balance(ivy, EUR(100)));
3173 env.require(balance(ivy, reserve(env, 3)));
3174 env.require(offers(ivy, 0));
3175
3176 env.require(balance(joe, USD(200)));
3177 env.require(balance(joe, EUR(100)));
3178 env.require(balance(joe, reserve(env, 3)));
3179 env.require(offers(joe, 0));
3180 }
3181 {
3182 // A trust line's QualityOut should not affect offer crossing.
3183 auto const kim = Account("kim");
3184 auto const K_BUX = kim["BUX"];
3185 auto const lex = Account("lex");
3186 auto const meg = Account("meg");
3187 auto const ned = Account("ned");
3188 auto const N_BUX = ned["BUX"];
3189
3190 // Verify trust line QualityOut affects payments.
3191 env.fund(reserve(env, 4) + (fee * 4), kim, lex, meg, ned);
3192 env.close();
3193
3194 env(trust(lex, K_BUX(400)));
3195 env(trust(lex, N_BUX(200)), qualityOutPercent(120));
3196 env(trust(meg, N_BUX(100)));
3197 env.close();
3198 env(pay(ned, lex, N_BUX(100)));
3199 env.close();
3200 env.require(balance(lex, N_BUX(100)));
3201
3202 env(pay(kim, meg, N_BUX(60)), path(lex, ned), sendmax(K_BUX(200)));
3203 env.close();
3204
3205 env.require(balance(kim, K_BUX(none)));
3206 env.require(balance(kim, N_BUX(none)));
3207 env.require(balance(lex, K_BUX(72)));
3208 env.require(balance(lex, N_BUX(40)));
3209 env.require(balance(meg, K_BUX(none)));
3210 env.require(balance(meg, N_BUX(60)));
3211 env.require(balance(ned, K_BUX(none)));
3212 env.require(balance(ned, N_BUX(none)));
3213
3214 // Now verify that offer crossing is unaffected by QualityOut.
3215 env(offer(lex, K_BUX(30), N_BUX(30)));
3216 env.close();
3217
3218 env(offer(kim, N_BUX(30), K_BUX(30)));
3219 env.close();
3220
3221 env.require(balance(kim, K_BUX(none)));
3222 env.require(balance(kim, N_BUX(30)));
3223 env.require(balance(lex, K_BUX(102)));
3224 env.require(balance(lex, N_BUX(10)));
3225 env.require(balance(meg, K_BUX(none)));
3226 env.require(balance(meg, N_BUX(60)));
3227 env.require(balance(ned, K_BUX(-30)));
3228 env.require(balance(ned, N_BUX(none)));
3229 }
3230 {
3231 // Make sure things work right when we're auto-bridging as well.
3232 auto const ova = Account("ova");
3233 auto const pat = Account("pat");
3234 auto const qae = Account("qae");
3235 env.fund(XRP(2) + reserve(env, 3) + (fee * 3), ova, pat, qae);
3236 env.close();
3237
3238 // o ova has USD but wants XRP.
3239 // o pat has XRP but wants EUR.
3240 // o qae has EUR but wants USD.
3241 env(trust(ova, USD(200)));
3242 env(trust(ova, EUR(200)));
3243 env(trust(pat, USD(200)));
3244 env(trust(pat, EUR(200)));
3245 env(trust(qae, USD(200)));
3246 env(trust(qae, EUR(200)));
3247 env.close();
3248
3249 env(pay(gw1, ova, USD(125)));
3250 env(pay(gw2, qae, EUR(150)));
3251 env.close();
3252
3253 env(offer(ova, XRP(2), USD(100)));
3254 env(offer(pat, EUR(100), XRP(2)));
3255 env.close();
3256
3257 env(offer(qae, USD(100), EUR(100)));
3258 env.close();
3259
3260 env.require(balance(ova, USD(0)));
3261 env.require(balance(ova, EUR(0)));
3262 env.require(balance(ova, XRP(4) + reserve(env, 3)));
3263
3264 // In pre-flow code ova's offer is left empty in the ledger.
3265 auto const ovasOffers = offersOnAccount(env, ova);
3266 if (ovasOffers.size() != 0)
3267 {
3268 BEAST_EXPECT(ovasOffers.size() == 1);
3269 auto const& ovasOffer = *(ovasOffers.front());
3270
3271 BEAST_EXPECT(ovasOffer[sfLedgerEntryType] == ltOFFER);
3272 BEAST_EXPECT(ovasOffer[sfTakerGets] == USD(0));
3273 BEAST_EXPECT(ovasOffer[sfTakerPays] == XRP(0));
3274 }
3275
3276 env.require(balance(pat, USD(0)));
3277 env.require(balance(pat, EUR(100)));
3278 env.require(balance(pat, XRP(0) + reserve(env, 3)));
3279 env.require(offers(pat, 0));
3280
3281 env.require(balance(qae, USD(100)));
3282 env.require(balance(qae, EUR(0)));
3283 env.require(balance(qae, XRP(2) + reserve(env, 3)));
3284 env.require(offers(qae, 0));
3285 }
3286 }
3287
3288 void
3290 {
3291 // The following test verifies some correct but slightly surprising
3292 // behavior in offer crossing. The scenario:
3293 //
3294 // o An entity has created one or more offers.
3295 // o The entity creates another offer that can be directly crossed
3296 // (not autobridged) by the previously created offer(s).
3297 // o Rather than self crossing the offers, delete the old offer(s).
3298 //
3299 // See a more complete explanation in the comments for
3300 // BookOfferCrossingStep::limitSelfCrossQuality().
3301 //
3302 // Note that, in this particular example, one offer causes several
3303 // crossable offers (worth considerably more than the new offer)
3304 // to be removed from the book.
3305 using namespace jtx;
3306
3307 auto const gw = Account("gateway");
3308 auto const USD = gw["USD"];
3309
3310 Env env{*this, features};
3311
3312 // The fee that's charged for transactions.
3313 auto const fee = env.current()->fees().base;
3314 auto const startBalance = XRP(1000000);
3315
3316 env.fund(startBalance + (fee * 4), gw);
3317 env.close();
3318
3319 env(offer(gw, USD(60), XRP(600)));
3320 env.close();
3321 env(offer(gw, USD(60), XRP(600)));
3322 env.close();
3323 env(offer(gw, USD(60), XRP(600)));
3324 env.close();
3325
3326 env.require(owners(gw, 3));
3327 env.require(balance(gw, startBalance + fee));
3328
3329 auto gwOffers = offersOnAccount(env, gw);
3330 BEAST_EXPECT(gwOffers.size() == 3);
3331 for (auto const& offerPtr : gwOffers)
3332 {
3333 auto const& offer = *offerPtr;
3334 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3335 BEAST_EXPECT(offer[sfTakerGets] == XRP(600));
3336 BEAST_EXPECT(offer[sfTakerPays] == USD(60));
3337 }
3338
3339 // Since this offer crosses the first offers, the previous offers
3340 // will be deleted and this offer will be put on the order book.
3341 env(offer(gw, XRP(1000), USD(100)));
3342 env.close();
3343 env.require(owners(gw, 1));
3344 env.require(offers(gw, 1));
3345 env.require(balance(gw, startBalance));
3346
3347 gwOffers = offersOnAccount(env, gw);
3348 BEAST_EXPECT(gwOffers.size() == 1);
3349 for (auto const& offerPtr : gwOffers)
3350 {
3351 auto const& offer = *offerPtr;
3352 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3353 BEAST_EXPECT(offer[sfTakerGets] == USD(100));
3354 BEAST_EXPECT(offer[sfTakerPays] == XRP(1000));
3355 }
3356 }
3357
3358 void
3360 {
3361 using namespace jtx;
3362
3363 auto const gw1 = Account("gateway1");
3364 auto const gw2 = Account("gateway2");
3365 auto const alice = Account("alice");
3366 auto const USD = gw1["USD"];
3367 auto const EUR = gw2["EUR"];
3368
3369 Env env{*this, features};
3370
3371 env.fund(XRP(1000000), gw1, gw2);
3372 env.close();
3373
3374 // The fee that's charged for transactions.
3375 auto const f = env.current()->fees().base;
3376
3377 // Test cases
3378 struct TestData
3379 {
3380 std::string acct; // Account operated on
3381 STAmount fundXRP; // XRP acct funded with
3382 STAmount fundUSD; // USD acct funded with
3383 STAmount fundEUR; // EUR acct funded with
3384 TER firstOfferTec; // tec code on first offer
3385 TER secondOfferTec; // tec code on second offer
3386 };
3387
3388 // clang-format off
3389 TestData const tests[]{
3390 // acct fundXRP fundUSD fundEUR firstOfferTec secondOfferTec
3391 {"ann", reserve(env, 3) + f * 4, USD(1000), EUR(1000), tesSUCCESS, tesSUCCESS},
3392 {"bev", reserve(env, 3) + f * 4, USD( 1), EUR(1000), tesSUCCESS, tesSUCCESS},
3393 {"cam", reserve(env, 3) + f * 4, USD(1000), EUR( 1), tesSUCCESS, tesSUCCESS},
3394 {"deb", reserve(env, 3) + f * 4, USD( 0), EUR( 1), tesSUCCESS, tecUNFUNDED_OFFER},
3395 {"eve", reserve(env, 3) + f * 4, USD( 1), EUR( 0), tecUNFUNDED_OFFER, tesSUCCESS},
3396 {"flo", reserve(env, 3) + 0, USD(1000), EUR(1000), tecINSUF_RESERVE_OFFER, tecINSUF_RESERVE_OFFER},
3397 };
3398 //clang-format on
3399
3400 for (auto const& t : tests)
3401 {
3402 auto const acct = Account{t.acct};
3403 env.fund(t.fundXRP, acct);
3404 env.close();
3405
3406 env(trust(acct, USD(1000)));
3407 env(trust(acct, EUR(1000)));
3408 env.close();
3409
3410 if (t.fundUSD > USD(0))
3411 env(pay(gw1, acct, t.fundUSD));
3412 if (t.fundEUR > EUR(0))
3413 env(pay(gw2, acct, t.fundEUR));
3414 env.close();
3415
3416 env(offer(acct, USD(500), EUR(600)), ter(t.firstOfferTec));
3417 env.close();
3418 std::uint32_t const firstOfferSeq = env.seq(acct) - 1;
3419
3420 int offerCount = t.firstOfferTec == tesSUCCESS ? 1 : 0;
3421 env.require(owners(acct, 2 + offerCount));
3422 env.require(balance(acct, t.fundUSD));
3423 env.require(balance(acct, t.fundEUR));
3424
3425 auto acctOffers = offersOnAccount(env, acct);
3426 BEAST_EXPECT(acctOffers.size() == offerCount);
3427 for (auto const& offerPtr : acctOffers)
3428 {
3429 auto const& offer = *offerPtr;
3430 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3431 BEAST_EXPECT(offer[sfTakerGets] == EUR(600));
3432 BEAST_EXPECT(offer[sfTakerPays] == USD(500));
3433 }
3434
3435 env(offer(acct, EUR(600), USD(500)), ter(t.secondOfferTec));
3436 env.close();
3437 std::uint32_t const secondOfferSeq = env.seq(acct) - 1;
3438
3439 offerCount = t.secondOfferTec == tesSUCCESS ? 1 : offerCount;
3440 env.require(owners(acct, 2 + offerCount));
3441 env.require(balance(acct, t.fundUSD));
3442 env.require(balance(acct, t.fundEUR));
3443
3444 acctOffers = offersOnAccount(env, acct);
3445 BEAST_EXPECT(acctOffers.size() == offerCount);
3446 for (auto const& offerPtr : acctOffers)
3447 {
3448 auto const& offer = *offerPtr;
3449 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3450 if (offer[sfSequence] == firstOfferSeq)
3451 {
3452 BEAST_EXPECT(offer[sfTakerGets] == EUR(600));
3453 BEAST_EXPECT(offer[sfTakerPays] == USD(500));
3454 }
3455 else
3456 {
3457 BEAST_EXPECT(offer[sfTakerGets] == USD(500));
3458 BEAST_EXPECT(offer[sfTakerPays] == EUR(600));
3459 }
3460 }
3461
3462 // Remove any offers from acct for the next pass.
3463 env(offer_cancel(acct, firstOfferSeq));
3464 env.close();
3465 env(offer_cancel(acct, secondOfferSeq));
3466 env.close();
3467 }
3468 }
3469
3470 void
3472 {
3473 testcase("Self Cross Offer");
3474 testSelfCrossOffer1(features);
3475 testSelfCrossOffer2(features);
3476 }
3477
3478 void
3480 {
3481 // Folks who issue their own currency have, in effect, as many
3482 // funds as they are trusted for. This test used to fail because
3483 // self-issuing was not properly checked. Verify that it works
3484 // correctly now.
3485 using namespace jtx;
3486
3487 Env env{*this, features};
3488
3489 auto const alice = Account("alice");
3490 auto const bob = Account("bob");
3491 auto const USD = bob["USD"];
3492 auto const f = env.current()->fees().base;
3493
3494 env.fund(XRP(50000) + f, alice, bob);
3495 env.close();
3496
3497 env(offer(alice, USD(5000), XRP(50000)));
3498 env.close();
3499
3500 // This offer should take alice's offer up to Alice's reserve.
3501 env(offer(bob, XRP(50000), USD(5000)));
3502 env.close();
3503
3504 // alice's offer should have been removed, since she's down to her
3505 // XRP reserve.
3506 env.require(balance(alice, XRP(250)));
3507 env.require(owners(alice, 1));
3508 env.require(lines(alice, 1));
3509
3510 // However bob's offer should be in the ledger, since it was not
3511 // fully crossed.
3512 auto const bobOffers = offersOnAccount(env, bob);
3513 BEAST_EXPECT(bobOffers.size() == 1);
3514 for (auto const& offerPtr : bobOffers)
3515 {
3516 auto const& offer = *offerPtr;
3517 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3518 BEAST_EXPECT(offer[sfTakerGets] == USD(25));
3519 BEAST_EXPECT(offer[sfTakerPays] == XRP(250));
3520 }
3521 }
3522
3523 void
3525 {
3526 // At one point in the past this invalid path caused an assert. It
3527 // should not be possible for user-supplied data to cause an assert.
3528 // Make sure the assert is gone.
3529 testcase("Bad path assert");
3530
3531 using namespace jtx;
3532
3533 Env env{*this, features};
3534
3535 // The fee that's charged for transactions.
3536 auto const fee = env.current()->fees().base;
3537 {
3538 // A trust line's QualityOut should not affect offer crossing.
3539 auto const ann = Account("ann");
3540 auto const A_BUX = ann["BUX"];
3541 auto const bob = Account("bob");
3542 auto const cam = Account("cam");
3543 auto const dan = Account("dan");
3544 auto const D_BUX = dan["BUX"];
3545
3546 // Verify trust line QualityOut affects payments.
3547 env.fund(reserve(env, 4) + (fee * 4), ann, bob, cam, dan);
3548 env.close();
3549
3550 env(trust(bob, A_BUX(400)));
3551 env(trust(bob, D_BUX(200)), qualityOutPercent(120));
3552 env(trust(cam, D_BUX(100)));
3553 env.close();
3554 env(pay(dan, bob, D_BUX(100)));
3555 env.close();
3556 env.require(balance(bob, D_BUX(100)));
3557
3558 env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200)));
3559 env.close();
3560
3561 env.require(balance(ann, A_BUX(none)));
3562 env.require(balance(ann, D_BUX(none)));
3563 env.require(balance(bob, A_BUX(72)));
3564 env.require(balance(bob, D_BUX(40)));
3565 env.require(balance(cam, A_BUX(none)));
3566 env.require(balance(cam, D_BUX(60)));
3567 env.require(balance(dan, A_BUX(none)));
3568 env.require(balance(dan, D_BUX(none)));
3569
3570 env(offer(bob, A_BUX(30), D_BUX(30)));
3571 env.close();
3572
3573 env(trust(ann, D_BUX(100)));
3574 env.close();
3575
3576 // This payment caused the assert.
3577 env(pay(ann, ann, D_BUX(30)),
3578 path(A_BUX, D_BUX),
3579 sendmax(A_BUX(30)),
3580 ter(temBAD_PATH));
3581 env.close();
3582
3583 env.require(balance(ann, A_BUX(none)));
3584 env.require(balance(ann, D_BUX(0)));
3585 env.require(balance(bob, A_BUX(72)));
3586 env.require(balance(bob, D_BUX(40)));
3587 env.require(balance(cam, A_BUX(none)));
3588 env.require(balance(cam, D_BUX(60)));
3589 env.require(balance(dan, A_BUX(0)));
3590 env.require(balance(dan, D_BUX(none)));
3591 }
3592 }
3593
3594 void
3596 {
3597 // The offer crossing code expects that a DirectStep is always
3598 // preceded by a BookStep. In one instance the default path
3599 // was not matching that assumption. Here we recreate that case
3600 // so we can prove the bug stays fixed.
3601 testcase("Direct to Direct path");
3602
3603 using namespace jtx;
3604
3605 Env env{*this, features};
3606
3607 auto const ann = Account("ann");
3608 auto const bob = Account("bob");
3609 auto const cam = Account("cam");
3610 auto const A_BUX = ann["BUX"];
3611 auto const B_BUX = bob["BUX"];
3612
3613 auto const fee = env.current()->fees().base;
3614 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
3615 env.close();
3616
3617 env(trust(ann, B_BUX(40)));
3618 env(trust(cam, A_BUX(40)));
3619 env(trust(cam, B_BUX(40)));
3620 env.close();
3621
3622 env(pay(ann, cam, A_BUX(35)));
3623 env(pay(bob, cam, B_BUX(35)));
3624
3625 env(offer(bob, A_BUX(30), B_BUX(30)));
3626 env.close();
3627
3628 // cam puts an offer on the books that her upcoming offer could cross.
3629 // But this offer should be deleted, not crossed, by her upcoming
3630 // offer.
3631 env(offer(cam, A_BUX(29), B_BUX(30), tfPassive));
3632 env.close();
3633 env.require(balance(cam, A_BUX(35)));
3634 env.require(balance(cam, B_BUX(35)));
3635 env.require(offers(cam, 1));
3636
3637 // This offer caused the assert.
3638 env(offer(cam, B_BUX(30), A_BUX(30)));
3639 env.close();
3640
3641 env.require(balance(bob, A_BUX(30)));
3642 env.require(balance(cam, A_BUX(5)));
3643 env.require(balance(cam, B_BUX(65)));
3644 env.require(offers(cam, 0));
3645 }
3646
3647 void
3649 {
3650 // The Flow offer crossing code used to assert if an offer was made
3651 // for more XRP than the offering account held. This unit test
3652 // reproduces that failing case.
3653 testcase("Self crossing low quality offer");
3654
3655 using namespace jtx;
3656
3657 Env env{*this, features};
3658
3659 auto const ann = Account("ann");
3660 auto const gw = Account("gateway");
3661 auto const BTC = gw["BTC"];
3662
3663 auto const fee = env.current()->fees().base;
3664 env.fund(reserve(env, 2) + drops(9999640) + (fee), ann);
3665 env.fund(reserve(env, 2) + (fee * 4), gw);
3666 env.close();
3667
3668 env(rate(gw, 1.002));
3669 env(trust(ann, BTC(10)));
3670 env.close();
3671
3672 env(pay(gw, ann, BTC(2.856)));
3673 env.close();
3674
3675 env(offer(ann, drops(365611702030), BTC(5.713)));
3676 env.close();
3677
3678 // This offer caused the assert.
3679 env(offer(ann, BTC(0.687), drops(20000000000)),
3681 }
3682
3683 void
3685 {
3686 // The Flow offer crossing code had a case where it was not rounding
3687 // the offer crossing correctly after a partial crossing. The
3688 // failing case was found on the network. Here we add the case to
3689 // the unit tests.
3690 testcase("Offer In Scaling");
3691
3692 using namespace jtx;
3693
3694 Env env{*this, features};
3695
3696 auto const gw = Account("gateway");
3697 auto const alice = Account("alice");
3698 auto const bob = Account("bob");
3699 auto const CNY = gw["CNY"];
3700
3701 auto const fee = env.current()->fees().base;
3702 env.fund(reserve(env, 2) + drops(400000000000) + (fee), alice, bob);
3703 env.fund(reserve(env, 2) + (fee * 4), gw);
3704 env.close();
3705
3706 env(trust(bob, CNY(500)));
3707 env.close();
3708
3709 env(pay(gw, bob, CNY(300)));
3710 env.close();
3711
3712 env(offer(bob, drops(5400000000), CNY(216.054)));
3713 env.close();
3714
3715 // This offer did not round result of partial crossing correctly.
3716 env(offer(alice, CNY(13562.0001), drops(339000000000)));
3717 env.close();
3718
3719 auto const aliceOffers = offersOnAccount(env, alice);
3720 BEAST_EXPECT(aliceOffers.size() == 1);
3721 for (auto const& offerPtr : aliceOffers)
3722 {
3723 auto const& offer = *offerPtr;
3724 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3725 BEAST_EXPECT(offer[sfTakerGets] == drops(333599446582));
3726 BEAST_EXPECT(offer[sfTakerPays] == CNY(13345.9461));
3727 }
3728 }
3729
3730 void
3732 {
3733 // After adding the previous case, there were still failing rounding
3734 // cases in Flow offer crossing. This one was because the gateway
3735 // transfer rate was not being correctly handled.
3736 testcase("Offer In Scaling With Xfer Rate");
3737
3738 using namespace jtx;
3739
3740 Env env{*this, features};
3741
3742 auto const gw = Account("gateway");
3743 auto const alice = Account("alice");
3744 auto const bob = Account("bob");
3745 auto const BTC = gw["BTC"];
3746 auto const JPY = gw["JPY"];
3747
3748 auto const fee = env.current()->fees().base;
3749 env.fund(reserve(env, 2) + drops(400000000000) + (fee), alice, bob);
3750 env.fund(reserve(env, 2) + (fee * 4), gw);
3751 env.close();
3752
3753 env(rate(gw, 1.002));
3754 env(trust(alice, JPY(4000)));
3755 env(trust(bob, BTC(2)));
3756 env.close();
3757
3758 env(pay(gw, alice, JPY(3699.034802280317)));
3759 env(pay(gw, bob, BTC(1.156722559140311)));
3760 env.close();
3761
3762 env(offer(bob, JPY(1241.913390770747), BTC(0.01969825690469254)));
3763 env.close();
3764
3765 // This offer did not round result of partial crossing correctly.
3766 env(offer(alice, BTC(0.05507568706427876), JPY(3472.696773391072)));
3767 env.close();
3768
3769 auto const aliceOffers = offersOnAccount(env, alice);
3770 BEAST_EXPECT(aliceOffers.size() == 1);
3771 for (auto const& offerPtr : aliceOffers)
3772 {
3773 auto const& offer = *offerPtr;
3774 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3775 BEAST_EXPECT(
3776 offer[sfTakerGets] ==
3777 STAmount(JPY.issue(), std::uint64_t(2230682446713524ul), -12));
3778 BEAST_EXPECT(offer[sfTakerPays] == BTC(0.035378));
3779 }
3780 }
3781
3782 void
3784 {
3785 // Another instance where Flow offer crossing was not always
3786 // working right was if the Taker had fewer funds than the Offer
3787 // was offering. The basis for this test came off the network.
3788 testcase("Offer Threshold With Reduced Funds");
3789
3790 using namespace jtx;
3791
3792 Env env{*this, features};
3793
3794 auto const gw1 = Account("gw1");
3795 auto const gw2 = Account("gw2");
3796 auto const alice = Account("alice");
3797 auto const bob = Account("bob");
3798 auto const USD = gw1["USD"];
3799 auto const JPY = gw2["JPY"];
3800
3801 auto const fee = env.current()->fees().base;
3802 env.fund(reserve(env, 2) + drops(400000000000) + (fee), alice, bob);
3803 env.fund(reserve(env, 2) + (fee * 4), gw1, gw2);
3804 env.close();
3805
3806 env(rate(gw1, 1.002));
3807 env(trust(alice, USD(1000)));
3808 env(trust(bob, JPY(100000)));
3809 env.close();
3810
3811 env(
3812 pay(gw1,
3813 alice,
3814 STAmount{USD.issue(), std::uint64_t(2185410179555600), -14}));
3815 env(
3816 pay(gw2,
3817 bob,
3818 STAmount{JPY.issue(), std::uint64_t(6351823459548956), -12}));
3819 env.close();
3820
3821 env(offer(
3822 bob,
3823 STAmount{USD.issue(), std::uint64_t(4371257532306000), -17},
3824 STAmount{JPY.issue(), std::uint64_t(4573216636606000), -15}));
3825 env.close();
3826
3827 // This offer did not partially cross correctly.
3828 env(offer(
3829 alice,
3830 STAmount{JPY.issue(), std::uint64_t(2291181510070762), -12},
3831 STAmount{USD.issue(), std::uint64_t(2190218999914694), -14}));
3832 env.close();
3833
3834 auto const aliceOffers = offersOnAccount(env, alice);
3835 BEAST_EXPECT(aliceOffers.size() == 1);
3836 for (auto const& offerPtr : aliceOffers)
3837 {
3838 auto const& offer = *offerPtr;
3839 BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER);
3840 BEAST_EXPECT(
3841 offer[sfTakerGets] ==
3842 STAmount(USD.issue(), std::uint64_t(2185847305256635), -14));
3843 BEAST_EXPECT(
3844 offer[sfTakerPays] ==
3845 STAmount(JPY.issue(), std::uint64_t(2286608293434156), -12));
3846 }
3847 }
3848
3849 void
3851 {
3852 testcase("Tiny Offer");
3853
3854 using namespace jtx;
3855
3856 Env env{*this, features};
3857
3858 auto const gw = Account("gw");
3859 auto const alice = Account("alice");
3860 auto const bob = Account("bob");
3861 auto const CNY = gw["CNY"];
3862 auto const fee = env.current()->fees().base;
3863 auto const startXrpBalance = drops(400000000000) + (fee * 2);
3864
3865 env.fund(startXrpBalance, gw, alice, bob);
3866 env.close();
3867
3868 env(trust(bob, CNY(100000)));
3869 env.close();
3870
3871 // Place alice's tiny offer in the book first. Let's see what happens
3872 // when a reasonable offer crosses it.
3873 STAmount const alicesCnyOffer{
3874 CNY.issue(), std::uint64_t(4926000000000000), -23};
3875
3876 env(offer(alice, alicesCnyOffer, drops(1), tfPassive));
3877 env.close();
3878
3879 // bob places an ordinary offer
3880 STAmount const bobsCnyStartBalance{
3881 CNY.issue(), std::uint64_t(3767479960090235), -15};
3882 env(pay(gw, bob, bobsCnyStartBalance));
3883 env.close();
3884
3885 env(offer(
3886 bob,
3887 drops(203),
3888 STAmount{CNY.issue(), std::uint64_t(1000000000000000), -20}));
3889 env.close();
3890
3891 env.require(balance(alice, alicesCnyOffer));
3892 env.require(balance(alice, startXrpBalance - fee - drops(1)));
3893 env.require(balance(bob, bobsCnyStartBalance - alicesCnyOffer));
3894 env.require(balance(bob, startXrpBalance - (fee * 2) + drops(1)));
3895 }
3896
3897 void
3899 {
3900 testcase("Self Pay Xfer Fee");
3901 // The old offer crossing code does not charge a transfer fee
3902 // if alice pays alice. That's different from how payments work.
3903 // Payments always charge a transfer fee even if the money is staying
3904 // in the same hands.
3905 //
3906 // What's an example where alice pays alice? There are three actors:
3907 // gw, alice, and bob.
3908 //
3909 // 1. gw issues BTC and USD. qw charges a 0.2% transfer fee.
3910 //
3911 // 2. alice makes an offer to buy XRP and sell USD.
3912 // 3. bob makes an offer to buy BTC and sell XRP.
3913 //
3914 // 4. alice now makes an offer to sell BTC and buy USD.
3915 //
3916 // This last offer crosses using auto-bridging.
3917 // o alice's last offer sells BTC to...
3918 // o bob' offer which takes alice's BTC and sells XRP to...
3919 // o alice's first offer which takes bob's XRP and sells USD to...
3920 // o alice's last offer.
3921 //
3922 // So alice sells USD to herself.
3923 //
3924 // There are six cases that we need to test:
3925 // o alice crosses her own offer on the first leg (BTC).
3926 // o alice crosses her own offer on the second leg (USD).
3927 // o alice crosses her own offers on both legs.
3928 // All three cases need to be tested:
3929 // o In reverse (alice has enough BTC to cover her offer) and
3930 // o Forward (alice owns less BTC than is in her final offer.
3931 //
3932 // It turns out that two of the forward cases fail for a different
3933 // reason. They are therefore commented out here, But they are
3934 // revisited in the testSelfPayUnlimitedFunds() unit test.
3935
3936 using namespace jtx;
3937
3938 Env env{*this, features};
3939 auto const baseFee = env.current()->fees().base.drops();
3940
3941 auto const gw = Account("gw");
3942 auto const BTC = gw["BTC"];
3943 auto const USD = gw["USD"];
3944 auto const startXrpBalance = XRP(4000000);
3945
3946 env.fund(startXrpBalance, gw);
3947 env.close();
3948
3949 env(rate(gw, 1.25));
3950 env.close();
3951
3952 // Test cases
3953 struct Actor
3954 {
3955 Account acct;
3956 int offers; // offers on account after crossing
3957 PrettyAmount xrp; // final expected after crossing
3958 PrettyAmount btc; // final expected after crossing
3959 PrettyAmount usd; // final expected after crossing
3960 };
3961 struct TestData
3962 {
3963 // The first three three integers give the *index* in actors
3964 // to assign each of the three roles. By using indices it is
3965 // easy for alice to own the offer in the first leg, the second
3966 // leg, or both.
3967 std::size_t self;
3968 std::size_t leg0;
3969 std::size_t leg1;
3970 PrettyAmount btcStart;
3971 std::vector<Actor> actors;
3972 };
3973
3974 // clang-format off
3975 TestData const tests[]{
3976 // btcStart --------------------- actor[0] --------------------- -------------------- actor[1] -------------------
3977 {0, 0, 1, BTC(20), {{"ann", 0, drops(3900000'000000 - 4 * baseFee), BTC(20.0), USD(3000)}, {"abe", 0, drops(4100000'000000 - 3 * baseFee), BTC( 0), USD(750)}}}, // no BTC xfer fee
3978 {0, 1, 0, BTC(20), {{"bev", 0, drops(4100000'000000 - 4 * baseFee), BTC( 7.5), USD(2000)}, {"bob", 0, drops(3900000'000000 - 3 * baseFee), BTC(10), USD( 0)}}}, // no USD xfer fee
3979 {0, 0, 0, BTC(20), {{"cam", 0, drops(4000000'000000 - 5 * baseFee), BTC(20.0), USD(2000)} }}, // no xfer fee
3980 {0, 1, 0, BTC( 5), {{"deb", 1, drops(4040000'000000 - 4 * baseFee), BTC( 0.0), USD(2000)}, {"dan", 1, drops(3960000'000000 - 3 * baseFee), BTC( 4), USD( 0)}}}, // no USD xfer fee
3981 };
3982 // clang-format on
3983
3984 for (auto const& t : tests)
3985 {
3986 Account const& self = t.actors[t.self].acct;
3987 Account const& leg0 = t.actors[t.leg0].acct;
3988 Account const& leg1 = t.actors[t.leg1].acct;
3989
3990 for (auto const& actor : t.actors)
3991 {
3992 env.fund(XRP(4000000), actor.acct);
3993 env.close();
3994
3995 env(trust(actor.acct, BTC(40)));
3996 env(trust(actor.acct, USD(8000)));
3997 env.close();
3998 }
3999
4000 env(pay(gw, self, t.btcStart));
4001 env(pay(gw, self, USD(2000)));
4002 if (self.id() != leg1.id())
4003 env(pay(gw, leg1, USD(2000)));
4004 env.close();
4005
4006 // Get the initial offers in place. Remember their sequences
4007 // so we can delete them later.
4008 env(offer(leg0, BTC(10), XRP(100000), tfPassive));
4009 env.close();
4010 std::uint32_t const leg0OfferSeq = env.seq(leg0) - 1;
4011
4012 env(offer(leg1, XRP(100000), USD(1000), tfPassive));
4013 env.close();
4014 std::uint32_t const leg1OfferSeq = env.seq(leg1) - 1;
4015
4016 // This is the offer that matters.
4017 env(offer(self, USD(1000), BTC(10)));
4018 env.close();
4019 std::uint32_t const selfOfferSeq = env.seq(self) - 1;
4020
4021 // Verify results.
4022 for (auto const& actor : t.actors)
4023 {
4024 // Sometimes Taker crossing gets lazy about deleting offers.
4025 // Treat an empty offer as though it is deleted.
4026 auto actorOffers = offersOnAccount(env, actor.acct);
4027 auto const offerCount = std::distance(
4028 actorOffers.begin(),
4030 actorOffers.begin(),
4031 actorOffers.end(),
4033 return (*offer)[sfTakerGets].signum() == 0;
4034 }));
4035 BEAST_EXPECT(offerCount == actor.offers);
4036
4037 env.require(balance(actor.acct, actor.xrp));
4038 env.require(balance(actor.acct, actor.btc));
4039 env.require(balance(actor.acct, actor.usd));
4040 }
4041 // Remove any offers that might be left hanging around. They
4042 // could bollix up later loops.
4043 env(offer_cancel(leg0, leg0OfferSeq));
4044 env.close();
4045 env(offer_cancel(leg1, leg1OfferSeq));
4046 env.close();
4047 env(offer_cancel(self, selfOfferSeq));
4048 env.close();
4049 }
4050 }
4051
4052 void
4054 {
4055 testcase("Self Pay Unlimited Funds");
4056 // The Taker offer crossing code recognized when Alice was paying
4057 // Alice the same denomination. In this case, as long as Alice
4058 // has a little bit of that denomination, it treats Alice as though
4059 // she has unlimited funds in that denomination.
4060 //
4061 // Huh? What kind of sense does that make?
4062 //
4063 // One way to think about it is to break a single payment into a
4064 // series of very small payments executed sequentially but very
4065 // quickly. Alice needs to pay herself 1 USD, but she only has
4066 // 0.01 USD. Alice says, "Hey Alice, let me pay you a penny."
4067 // Alice does this, taking the penny out of her pocket and then
4068 // putting it back in her pocket. Then she says, "Hey Alice,
4069 // I found another penny. I can pay you another penny." Repeat
4070 // these steps 100 times and Alice has paid herself 1 USD even though
4071 // she only owns 0.01 USD.
4072 //
4073 // That's all very nice, but the payment code does not support this
4074 // optimization. In part that's because the payment code can
4075 // operate on a whole batch of offers. As a matter of fact, it can
4076 // deal in two consecutive batches of offers. It would take a great
4077 // deal of sorting out to figure out which offers in the two batches
4078 // had the same owner and give them special processing. And,
4079 // honestly, it's a weird little corner case.
4080 //
4081 // So, since Flow offer crossing uses the payments engine, Flow
4082 // offer crossing no longer supports this optimization.
4083 //
4084 // The following test shows the difference in the behaviors between
4085 // Taker offer crossing and Flow offer crossing.
4086
4087 using namespace jtx;
4088
4089 Env env{*this, features};
4090 auto const baseFee = env.current()->fees().base.drops();
4091
4092 auto const gw = Account("gw");
4093 auto const BTC = gw["BTC"];
4094 auto const USD = gw["USD"];
4095 auto const startXrpBalance = XRP(4000000);
4096
4097 env.fund(startXrpBalance, gw);
4098 env.close();
4099
4100 env(rate(gw, 1.25));
4101 env.close();
4102
4103 // Test cases
4104 struct Actor
4105 {
4106 Account acct;
4107 int offers; // offers on account after crossing
4108 PrettyAmount xrp; // final expected after crossing
4109 PrettyAmount btc; // final expected after crossing
4110 PrettyAmount usd; // final expected after crossing
4111 };
4112 struct TestData
4113 {
4114 // The first three three integers give the *index* in actors
4115 // to assign each of the three roles. By using indices it is
4116 // easy for alice to own the offer in the first leg, the second
4117 // leg, or both.
4118 std::size_t self;
4119 std::size_t leg0;
4120 std::size_t leg1;
4121 PrettyAmount btcStart;
4122 std::vector<Actor> actors;
4123 };
4124
4125 // clang-format off
4126 TestData const tests[]{
4127 // btcStart ------------------- actor[0] -------------------- ------------------- actor[1] --------------------
4128 {0, 0, 1, BTC(5), {{"gay", 1, drops(3950000'000000 - 4 * baseFee), BTC(5), USD(2500)}, {"gar", 1, drops(4050000'000000 - 3 * baseFee), BTC(0), USD(1375)}}}, // no BTC xfer fee
4129 {0, 0, 0, BTC(5), {{"hye", 2, drops(4000000'000000 - 5 * baseFee), BTC(5), USD(2000)} }} // no xfer fee
4130 };
4131 // clang-format on
4132
4133 for (auto const& t : tests)
4134 {
4135 Account const& self = t.actors[t.self].acct;
4136 Account const& leg0 = t.actors[t.leg0].acct;
4137 Account const& leg1 = t.actors[t.leg1].acct;
4138
4139 for (auto const& actor : t.actors)
4140 {
4141 env.fund(XRP(4000000), actor.acct);
4142 env.close();
4143
4144 env(trust(actor.acct, BTC(40)));
4145 env(trust(actor.acct, USD(8000)));
4146 env.close();
4147 }
4148
4149 env(pay(gw, self, t.btcStart));
4150 env(pay(gw, self, USD(2000)));
4151 if (self.id() != leg1.id())
4152 env(pay(gw, leg1, USD(2000)));
4153 env.close();
4154
4155 // Get the initial offers in place. Remember their sequences
4156 // so we can delete them later.
4157 env(offer(leg0, BTC(10), XRP(100000), tfPassive));
4158 env.close();
4159 std::uint32_t const leg0OfferSeq = env.seq(leg0) - 1;
4160
4161 env(offer(leg1, XRP(100000), USD(1000), tfPassive));
4162 env.close();
4163 std::uint32_t const leg1OfferSeq = env.seq(leg1) - 1;
4164
4165 // This is the offer that matters.
4166 env(offer(self, USD(1000), BTC(10)));
4167 env.close();
4168 std::uint32_t const selfOfferSeq = env.seq(self) - 1;
4169
4170 // Verify results.
4171 for (auto const& actor : t.actors)
4172 {
4173 // Sometimes Taker offer crossing gets lazy about deleting
4174 // offers. Treat an empty offer as though it is deleted.
4175 auto actorOffers = offersOnAccount(env, actor.acct);
4176 auto const offerCount = std::distance(
4177 actorOffers.begin(),
4179 actorOffers.begin(),
4180 actorOffers.end(),
4182 return (*offer)[sfTakerGets].signum() == 0;
4183 }));
4184 BEAST_EXPECT(offerCount == actor.offers);
4185
4186 env.require(balance(actor.acct, actor.xrp));
4187 env.require(balance(actor.acct, actor.btc));
4188 env.require(balance(actor.acct, actor.usd));
4189 }
4190 // Remove any offers that might be left hanging around. They
4191 // could bollix up later loops.
4192 env(offer_cancel(leg0, leg0OfferSeq));
4193 env.close();
4194 env(offer_cancel(leg1, leg1OfferSeq));
4195 env.close();
4196 env(offer_cancel(self, selfOfferSeq));
4197 env.close();
4198 }
4199 }
4200
4201 void
4203 {
4204 testcase("lsfRequireAuth");
4205
4206 using namespace jtx;
4207
4208 Env env{*this, features};
4209
4210 auto const gw = Account("gw");
4211 auto const alice = Account("alice");
4212 auto const bob = Account("bob");
4213 auto const gwUSD = gw["USD"];
4214 auto const aliceUSD = alice["USD"];
4215 auto const bobUSD = bob["USD"];
4216
4217 env.fund(XRP(400000), gw, alice, bob);
4218 env.close();
4219
4220 // GW requires authorization for holders of its IOUs
4221 env(fset(gw, asfRequireAuth));
4222 env.close();
4223
4224 // Properly set trust and have gw authorize bob and alice
4225 env(trust(gw, bobUSD(100)), txflags(tfSetfAuth));
4226 env(trust(bob, gwUSD(100)));
4227 env(trust(gw, aliceUSD(100)), txflags(tfSetfAuth));
4228 env(trust(alice, gwUSD(100)));
4229 // Alice is able to place the offer since the GW has authorized her
4230 env(offer(alice, gwUSD(40), XRP(4000)));
4231 env.close();
4232
4233 env.require(offers(alice, 1));
4234 env.require(balance(alice, gwUSD(0)));
4235
4236 env(pay(gw, bob, gwUSD(50)));
4237 env.close();
4238
4239 env.require(balance(bob, gwUSD(50)));
4240
4241 // Bob's offer should cross Alice's
4242 env(offer(bob, XRP(4000), gwUSD(40)));
4243 env.close();
4244
4245 env.require(offers(alice, 0));
4246 env.require(balance(alice, gwUSD(40)));
4247
4248 env.require(offers(bob, 0));
4249 env.require(balance(bob, gwUSD(10)));
4250 }
4251
4252 void
4254 {
4255 testcase("Missing Auth");
4256 // 1. alice creates an offer to acquire USD/gw, an asset for which
4257 // she does not have a trust line. At some point in the future,
4258 // gw adds lsfRequireAuth. Then, later, alice's offer is crossed.
4259 // Alice's offer is deleted, not consumed, since alice is not
4260 // authorized to hold USD/gw.
4261 //
4262 // 2. alice tries to create an offer for USD/gw, now that gw has
4263 // lsfRequireAuth set. This time the offer create fails because
4264 // alice is not authorized to hold USD/gw.
4265 //
4266 // 3. Next, gw creates a trust line to alice, but does not set
4267 // tfSetfAuth on that trust line. alice attempts to create an
4268 // offer and again fails.
4269 //
4270 // 4. Finally, gw sets tsfSetAuth on the trust line authorizing
4271 // alice to own USD/gw. At this point alice successfully
4272 // creates and crosses an offer for USD/gw.
4273
4274 using namespace jtx;
4275
4276 Env env{*this, features};
4277
4278 auto const gw = Account("gw");
4279 auto const alice = Account("alice");
4280 auto const bob = Account("bob");
4281 auto const gwUSD = gw["USD"];
4282 auto const aliceUSD = alice["USD"];
4283 auto const bobUSD = bob["USD"];
4284
4285 env.fund(XRP(400000), gw, alice, bob);
4286 env.close();
4287
4288 env(offer(alice, gwUSD(40), XRP(4000)));
4289 env.close();
4290
4291 env.require(offers(alice, 1));
4292 env.require(balance(alice, gwUSD(none)));
4293 env(fset(gw, asfRequireAuth));
4294 env.close();
4295
4296 env(trust(gw, bobUSD(100)), txflags(tfSetfAuth));
4297 env.close();
4298 env(trust(bob, gwUSD(100)));
4299 env.close();
4300
4301 env(pay(gw, bob, gwUSD(50)));
4302 env.close();
4303 env.require(balance(bob, gwUSD(50)));
4304
4305 // gw now requires authorization and bob has gwUSD(50). Let's see if
4306 // bob can cross alice's offer.
4307 //
4308 // Bob's offer shouldn't cross and alice's unauthorized offer should be
4309 // deleted.
4310 env(offer(bob, XRP(4000), gwUSD(40)));
4311 env.close();
4312 std::uint32_t const bobOfferSeq = env.seq(bob) - 1;
4313
4314 env.require(offers(alice, 0));
4315 // alice's unauthorized offer is deleted & bob's offer not crossed.
4316 env.require(balance(alice, gwUSD(none)));
4317 env.require(offers(bob, 1));
4318 env.require(balance(bob, gwUSD(50)));
4319
4320 // See if alice can create an offer without authorization. alice
4321 // should not be able to create the offer and bob's offer should be
4322 // untouched.
4323 env(offer(alice, gwUSD(40), XRP(4000)), ter(tecNO_LINE));
4324 env.close();
4325
4326 env.require(offers(alice, 0));
4327 env.require(balance(alice, gwUSD(none)));
4328
4329 env.require(offers(bob, 1));
4330 env.require(balance(bob, gwUSD(50)));
4331
4332 // Set up a trust line for alice, but don't authorize it. alice
4333 // should still not be able to create an offer for USD/gw.
4334 env(trust(gw, aliceUSD(100)));
4335 env.close();
4336
4337 env(offer(alice, gwUSD(40), XRP(4000)), ter(tecNO_AUTH));
4338 env.close();
4339
4340 env.require(offers(alice, 0));
4341 env.require(balance(alice, gwUSD(0)));
4342
4343 env.require(offers(bob, 1));
4344 env.require(balance(bob, gwUSD(50)));
4345
4346 // Delete bob's offer so alice can create an offer without crossing.
4347 env(offer_cancel(bob, bobOfferSeq));
4348 env.close();
4349 env.require(offers(bob, 0));
4350
4351 // Finally, set up an authorized trust line for alice. Now alice's
4352 // offer should succeed. Note that, since this is an offer rather
4353 // than a payment, alice does not need to set a trust line limit.
4354 env(trust(gw, aliceUSD(100)), txflags(tfSetfAuth));
4355 env.close();
4356
4357 env(offer(alice, gwUSD(40), XRP(4000)));
4358 env.close();
4359
4360 env.require(offers(alice, 1));
4361
4362 // Now bob creates his offer again. alice's offer should cross.
4363 env(offer(bob, XRP(4000), gwUSD(40)));
4364 env.close();
4365
4366 env.require(offers(alice, 0));
4367 env.require(balance(alice, gwUSD(40)));
4368
4369 env.require(offers(bob, 0));
4370 env.require(balance(bob, gwUSD(10)));
4371 }
4372
4373 void
4375 {
4376 testcase("RippleConnect Smoketest payment flow");
4377 using namespace jtx;
4378
4379 Env env{*this, features};
4380
4381 // This test mimics the payment flow used in the Ripple Connect
4382 // smoke test. The players:
4383 // A USD gateway with hot and cold wallets
4384 // A EUR gateway with hot and cold walllets
4385 // A MM gateway that will provide offers from USD->EUR and EUR->USD
4386 // A path from hot US to cold EUR is found and then used to send
4387 // USD for EUR that goes through the market maker
4388
4389 auto const hotUS = Account("hotUS");
4390 auto const coldUS = Account("coldUS");
4391 auto const hotEU = Account("hotEU");
4392 auto const coldEU = Account("coldEU");
4393 auto const mm = Account("mm");
4394
4395 auto const USD = coldUS["USD"];
4396 auto const EUR = coldEU["EUR"];
4397
4398 env.fund(XRP(100000), hotUS, coldUS, hotEU, coldEU, mm);
4399 env.close();
4400
4401 // Cold wallets require trust but will ripple by default
4402 for (auto const& cold : {coldUS, coldEU})
4403 {
4404 env(fset(cold, asfRequireAuth));
4405 env(fset(cold, asfDefaultRipple));
4406 }
4407 env.close();
4408
4409 // Each hot wallet trusts the related cold wallet for a large amount
4410 env(trust(hotUS, USD(10000000)), txflags(tfSetNoRipple));
4411 env(trust(hotEU, EUR(10000000)), txflags(tfSetNoRipple));
4412 // Market maker trusts both cold wallets for a large amount
4413 env(trust(mm, USD(10000000)), txflags(tfSetNoRipple));
4414 env(trust(mm, EUR(10000000)), txflags(tfSetNoRipple));
4415 env.close();
4416
4417 // Gateways authorize the trustlines of hot and market maker
4418 env(trust(coldUS, USD(0), hotUS, tfSetfAuth));
4419 env(trust(coldEU, EUR(0), hotEU, tfSetfAuth));
4420 env(trust(coldUS, USD(0), mm, tfSetfAuth));
4421 env(trust(coldEU, EUR(0), mm, tfSetfAuth));
4422 env.close();
4423
4424 // Issue currency from cold wallets to hot and market maker
4425 env(pay(coldUS, hotUS, USD(5000000)));
4426 env(pay(coldEU, hotEU, EUR(5000000)));
4427 env(pay(coldUS, mm, USD(5000000)));
4428 env(pay(coldEU, mm, EUR(5000000)));
4429 env.close();
4430
4431 // MM places offers
4432 float const rate = 0.9f; // 0.9 USD = 1 EUR
4433 env(offer(mm, EUR(4000000 * rate), USD(4000000)),
4434 json(jss::Flags, tfSell));
4435
4436 float const reverseRate = 1.0f / rate * 1.00101f;
4437 env(offer(mm, USD(4000000 * reverseRate), EUR(4000000)),
4438 json(jss::Flags, tfSell));
4439 env.close();
4440
4441 // There should be a path available from hot US to cold EUR
4442 {
4443 Json::Value jvParams;
4444 jvParams[jss::destination_account] = coldEU.human();
4445 jvParams[jss::destination_amount][jss::issuer] = coldEU.human();
4446 jvParams[jss::destination_amount][jss::currency] = "EUR";
4447 jvParams[jss::destination_amount][jss::value] = 10;
4448 jvParams[jss::source_account] = hotUS.human();
4449
4450 Json::Value const jrr{env.rpc(
4451 "json", "ripple_path_find", to_string(jvParams))[jss::result]};
4452
4453 BEAST_EXPECT(jrr[jss::status] == "success");
4454 BEAST_EXPECT(
4455 jrr[jss::alternatives].isArray() &&
4456 jrr[jss::alternatives].size() > 0);
4457 }
4458 // Send the payment using the found path.
4459 env(pay(hotUS, coldEU, EUR(10)), sendmax(USD(11.1223326)));
4460 }
4461
4462 void
4464 {
4465 testcase("Self Auth");
4466
4467 using namespace jtx;
4468
4469 Env env{*this, features};
4470
4471 auto const gw = Account("gw");
4472 auto const alice = Account("alice");
4473 auto const gwUSD = gw["USD"];
4474 auto const aliceUSD = alice["USD"];
4475
4476 env.fund(XRP(400000), gw, alice);
4477 env.close();
4478
4479 // Test that gw can create an offer to buy gw's currency.
4480 env(offer(gw, gwUSD(40), XRP(4000)));
4481 env.close();
4482 std::uint32_t const gwOfferSeq = env.seq(gw) - 1;
4483 env.require(offers(gw, 1));
4484
4485 // Since gw has an offer out, gw should not be able to set RequireAuth.
4486 env(fset(gw, asfRequireAuth), ter(tecOWNERS));
4487 env.close();
4488
4489 // Cancel gw's offer so we can set RequireAuth.
4490 env(offer_cancel(gw, gwOfferSeq));
4491 env.close();
4492 env.require(offers(gw, 0));
4493
4494 // gw now requires authorization for holders of its IOUs
4495 env(fset(gw, asfRequireAuth));
4496 env.close();
4497
4498 // Before DepositPreauth an account with lsfRequireAuth set could not
4499 // create an offer to buy their own currency. After DepositPreauth
4500 // they can.
4501 env(offer(gw, gwUSD(40), XRP(4000)), ter(tesSUCCESS));
4502 env.close();
4503
4504 env.require(offers(gw, 1));
4505
4506 // Set up an authorized trust line and pay alice gwUSD 50.
4507 env(trust(gw, aliceUSD(100)), txflags(tfSetfAuth));
4508 env(trust(alice, gwUSD(100)));
4509 env.close();
4510
4511 env(pay(gw, alice, gwUSD(50)));
4512 env.close();
4513
4514 env.require(balance(alice, gwUSD(50)));
4515
4516 // alice's offer should cross gw's
4517 env(offer(alice, XRP(4000), gwUSD(40)));
4518 env.close();
4519
4520 env.require(offers(alice, 0));
4521 env.require(balance(alice, gwUSD(10)));
4522
4523 env.require(offers(gw, 0));
4524 }
4525
4526 void
4528 {
4529 // Show that an offer who's issuer has been deleted cannot be crossed.
4530 using namespace jtx;
4531
4532 testcase("Deleted offer issuer");
4533
4534 auto trustLineExists = [](jtx::Env const& env,
4535 jtx::Account const& src,
4536 jtx::Account const& dst,
4537 Currency const& cur) -> bool {
4538 return bool(env.le(keylet::line(src, dst, cur)));
4539 };
4540
4541 Account const alice("alice");
4542 Account const becky("becky");
4543 Account const carol("carol");
4544 Account const gw("gateway");
4545 auto const USD = gw["USD"];
4546 auto const BUX = alice["BUX"];
4547
4548 Env env{*this, features};
4549
4550 env.fund(XRP(10000), alice, becky, carol, noripple(gw));
4551 env.close();
4552 env.trust(USD(1000), becky);
4553 env(pay(gw, becky, USD(5)));
4554 env.close();
4555 BEAST_EXPECT(trustLineExists(env, gw, becky, USD.currency));
4556
4557 // Make offers that produce USD and can be crossed two ways:
4558 // direct XRP -> USD
4559 // direct BUX -> USD
4560 env(offer(becky, XRP(2), USD(2)), txflags(tfPassive));
4561 std::uint32_t const beckyBuxUsdSeq{env.seq(becky)};
4562 env(offer(becky, BUX(3), USD(3)), txflags(tfPassive));
4563 env.close();
4564
4565 // becky keeps the offers, but removes the trustline.
4566 env(pay(becky, gw, USD(5)));
4567 env.trust(USD(0), becky);
4568 env.close();
4569 BEAST_EXPECT(!trustLineExists(env, gw, becky, USD.currency));
4570 BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2)));
4571 BEAST_EXPECT(isOffer(env, becky, BUX(3), USD(3)));
4572
4573 // Delete gw's account.
4574 {
4575 // The ledger sequence needs to far enough ahead of the account
4576 // sequence before the account can be deleted.
4577 int const delta =
4578 [&env, &gw, openLedgerSeq = env.current()->seq()]() -> int {
4579 std::uint32_t const gwSeq{env.seq(gw)};
4580 if (gwSeq + 255 > openLedgerSeq)
4581 return gwSeq - openLedgerSeq + 255;
4582 return 0;
4583 }();
4584
4585 for (int i = 0; i < delta; ++i)
4586 env.close();
4587
4588 // Account deletion has a high fee. Account for that.
4589 env(acctdelete(gw, alice),
4590 fee(drops(env.current()->fees().increment)));
4591 env.close();
4592
4593 // Verify that gw's account root is gone from the ledger.
4594 BEAST_EXPECT(!env.closed()->exists(keylet::account(gw.id())));
4595 }
4596
4597 // alice crosses becky's first offer. The offer create fails because
4598 // the USD issuer is not in the ledger.
4599 env(offer(alice, USD(2), XRP(2)), ter(tecNO_ISSUER));
4600 env.close();
4601 env.require(offers(alice, 0));
4602 BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2)));
4603 BEAST_EXPECT(isOffer(env, becky, BUX(3), USD(3)));
4604
4605 // alice crosses becky's second offer. Again, the offer create fails
4606 // because the USD issuer is not in the ledger.
4607 env(offer(alice, USD(3), BUX(3)), ter(tecNO_ISSUER));
4608 env.require(offers(alice, 0));
4609 BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2)));
4610 BEAST_EXPECT(isOffer(env, becky, BUX(3), USD(3)));
4611
4612 // Cancel becky's BUX -> USD offer so we can try auto-bridging.
4613 env(offer_cancel(becky, beckyBuxUsdSeq));
4614 env.close();
4615 BEAST_EXPECT(!isOffer(env, becky, BUX(3), USD(3)));
4616
4617 // alice creates an offer that can be auto-bridged with becky's
4618 // remaining offer.
4619 env.trust(BUX(1000), carol);
4620 env(pay(alice, carol, BUX(2)));
4621
4622 env(offer(alice, BUX(2), XRP(2)));
4623 env.close();
4624
4625 // carol attempts the auto-bridge. Again, the offer create fails
4626 // because the USD issuer is not in the ledger.
4627 env(offer(carol, USD(2), BUX(2)), ter(tecNO_ISSUER));
4628 env.close();
4629 BEAST_EXPECT(isOffer(env, alice, BUX(2), XRP(2)));
4630 BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2)));
4631 }
4632
4633 void
4635 {
4636 testcase("Tick Size");
4637
4638 using namespace jtx;
4639
4640 // Try to set tick size out of range
4641 {
4642 Env env{*this, features};
4643 auto const gw = Account{"gateway"};
4644 env.fund(XRP(10000), gw);
4645 env.close();
4646
4647 auto txn = noop(gw);
4648 txn[sfTickSize.fieldName] = Quality::minTickSize - 1;
4649 env(txn, ter(temBAD_TICK_SIZE));
4650
4651 txn[sfTickSize.fieldName] = Quality::minTickSize;
4652 env(txn);
4653 BEAST_EXPECT((*env.le(gw))[sfTickSize] == Quality::minTickSize);
4654
4655 txn = noop(gw);
4656 txn[sfTickSize.fieldName] = Quality::maxTickSize;
4657 env(txn);
4658 BEAST_EXPECT(!env.le(gw)->isFieldPresent(sfTickSize));
4659
4660 txn = noop(gw);
4661 txn[sfTickSize.fieldName] = Quality::maxTickSize - 1;
4662 env(txn);
4663 BEAST_EXPECT((*env.le(gw))[sfTickSize] == Quality::maxTickSize - 1);
4664
4665 txn = noop(gw);
4666 txn[sfTickSize.fieldName] = Quality::maxTickSize + 1;
4667 env(txn, ter(temBAD_TICK_SIZE));
4668
4669 txn[sfTickSize.fieldName] = 0;
4670 env(txn);
4671 BEAST_EXPECT(!env.le(gw)->isFieldPresent(sfTickSize));
4672 }
4673
4674 Env env{*this, features};
4675 auto const gw = Account{"gateway"};
4676 auto const alice = Account{"alice"};
4677 auto const XTS = gw["XTS"];
4678 auto const XXX = gw["XXX"];
4679
4680 env.fund(XRP(10000), gw, alice);
4681 env.close();
4682
4683 {
4684 // Gateway sets its tick size to 5
4685 auto txn = noop(gw);
4686 txn[sfTickSize.fieldName] = 5;
4687 env(txn);
4688 BEAST_EXPECT((*env.le(gw))[sfTickSize] == 5);
4689 }
4690
4691 env(trust(alice, XTS(1000)));
4692 env(trust(alice, XXX(1000)));
4693
4694 env(pay(gw, alice, alice["XTS"](100)));
4695 env(pay(gw, alice, alice["XXX"](100)));
4696
4697 env(offer(alice, XTS(10), XXX(30)));
4698 env(offer(alice, XTS(30), XXX(10)));
4699 env(offer(alice, XTS(10), XXX(30)), json(jss::Flags, tfSell));
4700 env(offer(alice, XTS(30), XXX(10)), json(jss::Flags, tfSell));
4701
4704 *env.current(), alice, [&](std::shared_ptr<SLE const> const& sle) {
4705 if (sle->getType() == ltOFFER)
4706 offers.emplace(
4707 (*sle)[sfSequence],
4708 std::make_pair(
4709 (*sle)[sfTakerPays], (*sle)[sfTakerGets]));
4710 });
4711
4712 // first offer
4713 auto it = offers.begin();
4714 BEAST_EXPECT(it != offers.end());
4715 BEAST_EXPECT(
4716 it->second.first == XTS(10) && it->second.second < XXX(30) &&
4717 it->second.second > XXX(29.9994));
4718
4719 // second offer
4720 ++it;
4721 BEAST_EXPECT(it != offers.end());
4722 BEAST_EXPECT(
4723 it->second.first == XTS(30) && it->second.second == XXX(10));
4724
4725 // third offer
4726 ++it;
4727 BEAST_EXPECT(it != offers.end());
4728 BEAST_EXPECT(
4729 it->second.first == XTS(10.0002) && it->second.second == XXX(30));
4730
4731 // fourth offer
4732 // exact TakerPays is XTS(1/.033333)
4733 ++it;
4734 BEAST_EXPECT(it != offers.end());
4735 BEAST_EXPECT(
4736 it->second.first == XTS(30) && it->second.second == XXX(10));
4737
4738 BEAST_EXPECT(++it == offers.end());
4739 }
4740
4741 // Helper function that returns offers on an account sorted by sequence.
4744 {
4746 offersOnAccount(env, acct)};
4747 std::sort(
4748 offers.begin(),
4749 offers.end(),
4750 [](std::shared_ptr<SLE const> const& rhs,
4751 std::shared_ptr<SLE const> const& lhs) {
4752 return (*rhs)[sfSequence] < (*lhs)[sfSequence];
4753 });
4754 return offers;
4755 }
4756
4757 void
4759 {
4760 testcase("Ticket Offers");
4761
4762 using namespace jtx;
4763
4764 // Two goals for this test.
4765 //
4766 // o Verify that offers can be created using tickets.
4767 //
4768 // o Show that offers in the _same_ order book remain in
4769 // chronological order regardless of sequence/ticket numbers.
4770 Env env{*this, features};
4771 auto const gw = Account{"gateway"};
4772 auto const alice = Account{"alice"};
4773 auto const bob = Account{"bob"};
4774 auto const USD = gw["USD"];
4775
4776 env.fund(XRP(10000), gw, alice, bob);
4777 env.close();
4778
4779 env(trust(alice, USD(1000)));
4780 env(trust(bob, USD(1000)));
4781 env.close();
4782
4783 env(pay(gw, alice, USD(200)));
4784 env.close();
4785
4786 // Create four offers from the same account with identical quality
4787 // so they go in the same order book. Each offer goes in a different
4788 // ledger so the chronology is clear.
4789 std::uint32_t const offerId_0{env.seq(alice)};
4790 env(offer(alice, XRP(50), USD(50)));
4791 env.close();
4792
4793 // Create two tickets.
4794 std::uint32_t const ticketSeq{env.seq(alice) + 1};
4795 env(ticket::create(alice, 2));
4796 env.close();
4797
4798 // Create another sequence-based offer.
4799 std::uint32_t const offerId_1{env.seq(alice)};
4800 BEAST_EXPECT(offerId_1 == offerId_0 + 4);
4801 env(offer(alice, XRP(50), USD(50)));
4802 env.close();
4803
4804 // Create two ticket based offers in reverse order.
4805 std::uint32_t const offerId_2{ticketSeq + 1};
4806 env(offer(alice, XRP(50), USD(50)), ticket::use(offerId_2));
4807 env.close();
4808
4809 // Create the last offer.
4810 std::uint32_t const offerId_3{ticketSeq};
4811 env(offer(alice, XRP(50), USD(50)), ticket::use(offerId_3));
4812 env.close();
4813
4814 // Verify that all of alice's offers are present.
4815 {
4816 auto offers = sortedOffersOnAccount(env, alice);
4817 BEAST_EXPECT(offers.size() == 4);
4818 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_0);
4819 BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerId_3);
4820 BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerId_2);
4821 BEAST_EXPECT(offers[3]->getFieldU32(sfSequence) == offerId_1);
4822 env.require(balance(alice, USD(200)));
4823 env.require(owners(alice, 5));
4824 }
4825
4826 // Cross alice's first offer.
4827 env(offer(bob, USD(50), XRP(50)));
4828 env.close();
4829
4830 // Verify that the first offer alice created was consumed.
4831 {
4832 auto offers = sortedOffersOnAccount(env, alice);
4833 BEAST_EXPECT(offers.size() == 3);
4834 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_3);
4835 BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerId_2);
4836 BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerId_1);
4837 }
4838
4839 // Cross alice's second offer.
4840 env(offer(bob, USD(50), XRP(50)));
4841 env.close();
4842
4843 // Verify that the second offer alice created was consumed.
4844 {
4845 auto offers = sortedOffersOnAccount(env, alice);
4846 BEAST_EXPECT(offers.size() == 2);
4847 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_3);
4848 BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerId_2);
4849 }
4850
4851 // Cross alice's third offer.
4852 env(offer(bob, USD(50), XRP(50)));
4853 env.close();
4854
4855 // Verify that the third offer alice created was consumed.
4856 {
4857 auto offers = sortedOffersOnAccount(env, alice);
4858 BEAST_EXPECT(offers.size() == 1);
4859 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_3);
4860 }
4861
4862 // Cross alice's last offer.
4863 env(offer(bob, USD(50), XRP(50)));
4864 env.close();
4865
4866 // Verify that the third offer alice created was consumed.
4867 {
4868 auto offers = sortedOffersOnAccount(env, alice);
4869 BEAST_EXPECT(offers.size() == 0);
4870 }
4871 env.require(balance(alice, USD(0)));
4872 env.require(owners(alice, 1));
4873 env.require(balance(bob, USD(200)));
4874 env.require(owners(bob, 1));
4875 }
4876
4877 void
4879 {
4880 testcase("Ticket Cancel Offers");
4881
4882 using namespace jtx;
4883
4884 // Verify that offers created with or without tickets can be canceled
4885 // by transactions with or without tickets.
4886 Env env{*this, features};
4887 auto const gw = Account{"gateway"};
4888 auto const alice = Account{"alice"};
4889 auto const USD = gw["USD"];
4890
4891 env.fund(XRP(10000), gw, alice);
4892 env.close();
4893
4894 env(trust(alice, USD(1000)));
4895 env.close();
4896 env.require(owners(alice, 1), tickets(alice, 0));
4897
4898 env(pay(gw, alice, USD(200)));
4899 env.close();
4900
4901 // Create the first of four offers using a sequence.
4902 std::uint32_t const offerSeqId_0{env.seq(alice)};
4903 env(offer(alice, XRP(50), USD(50)));
4904 env.close();
4905 env.require(owners(alice, 2), tickets(alice, 0));
4906
4907 // Create four tickets.
4908 std::uint32_t const ticketSeq{env.seq(alice) + 1};
4909 env(ticket::create(alice, 4));
4910 env.close();
4911 env.require(owners(alice, 6), tickets(alice, 4));
4912
4913 // Create the second (also sequence-based) offer.
4914 std::uint32_t const offerSeqId_1{env.seq(alice)};
4915 BEAST_EXPECT(offerSeqId_1 == offerSeqId_0 + 6);
4916 env(offer(alice, XRP(50), USD(50)));
4917 env.close();
4918
4919 // Create the third (ticket-based) offer.
4920 std::uint32_t const offerTixId_0{ticketSeq + 1};
4921 env(offer(alice, XRP(50), USD(50)), ticket::use(offerTixId_0));
4922 env.close();
4923
4924 // Create the last offer.
4925 std::uint32_t const offerTixId_1{ticketSeq};
4926 env(offer(alice, XRP(50), USD(50)), ticket::use(offerTixId_1));
4927 env.close();
4928
4929 // Verify that all of alice's offers are present.
4930 {
4931 auto offers = sortedOffersOnAccount(env, alice);
4932 BEAST_EXPECT(offers.size() == 4);
4933 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerSeqId_0);
4934 BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerTixId_1);
4935 BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerTixId_0);
4936 BEAST_EXPECT(offers[3]->getFieldU32(sfSequence) == offerSeqId_1);
4937 env.require(balance(alice, USD(200)));
4938 env.require(owners(alice, 7));
4939 }
4940
4941 // Use a ticket to cancel an offer created with a sequence.
4942 env(offer_cancel(alice, offerSeqId_0), ticket::use(ticketSeq + 2));
4943 env.close();
4944
4945 // Verify that offerSeqId_0 was canceled.
4946 {
4947 auto offers = sortedOffersOnAccount(env, alice);
4948 BEAST_EXPECT(offers.size() == 3);
4949 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerTixId_1);
4950 BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerTixId_0);
4951 BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerSeqId_1);
4952 }
4953
4954 // Use a ticket to cancel an offer created with a ticket.
4955 env(offer_cancel(alice, offerTixId_0), ticket::use(ticketSeq + 3));
4956 env.close();
4957
4958 // Verify that offerTixId_0 was canceled.
4959 {
4960 auto offers = sortedOffersOnAccount(env, alice);
4961 BEAST_EXPECT(offers.size() == 2);
4962 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerTixId_1);
4963 BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerSeqId_1);
4964 }
4965
4966 // All of alice's tickets should now be used up.
4967 env.require(owners(alice, 3), tickets(alice, 0));
4968
4969 // Use a sequence to cancel an offer created with a ticket.
4970 env(offer_cancel(alice, offerTixId_1));
4971 env.close();
4972
4973 // Verify that offerTixId_1 was canceled.
4974 {
4975 auto offers = sortedOffersOnAccount(env, alice);
4976 BEAST_EXPECT(offers.size() == 1);
4977 BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerSeqId_1);
4978 }
4979
4980 // Use a sequence to cancel an offer created with a sequence.
4981 env(offer_cancel(alice, offerSeqId_1));
4982 env.close();
4983
4984 // Verify that offerSeqId_1 was canceled.
4985 // All of alice's tickets should now be used up.
4986 env.require(owners(alice, 1), tickets(alice, 0), offers(alice, 0));
4987 }
4988
4989 void
4991 {
4992 // An assert was falsely triggering when computing rates for offers.
4993 // This unit test would trigger that assert (which has been removed).
4994 testcase("incorrect assert fixed");
4995 using namespace jtx;
4996
4997 Env env{*this};
4998 auto const alice = Account("alice");
4999 auto const USD = alice["USD"];
5000
5001 env.fund(XRP(10000), alice);
5002 env.close();
5003 env(offer(alice, XRP(100000000000), USD(100000000)));
5004 pass();
5005 }
5006
5007 void
5009 {
5010 testcase("fixFillOrKill");
5011 using namespace jtx;
5012 Env env(*this, features);
5013 Account const issuer("issuer");
5014 Account const maker("maker");
5015 Account const taker("taker");
5016 auto const USD = issuer["USD"];
5017 auto const EUR = issuer["EUR"];
5018
5019 env.fund(XRP(1'000), issuer);
5020 env.fund(XRP(1'000), maker, taker);
5021 env.close();
5022
5023 env.trust(USD(1'000), maker, taker);
5024 env.trust(EUR(1'000), maker, taker);
5025 env.close();
5026
5027 env(pay(issuer, maker, USD(1'000)));
5028 env(pay(issuer, taker, USD(1'000)));
5029 env(pay(issuer, maker, EUR(1'000)));
5030 env.close();
5031
5032 auto makerUSDBalance = env.balance(maker, USD).value();
5033 auto takerUSDBalance = env.balance(taker, USD).value();
5034 auto makerEURBalance = env.balance(maker, EUR).value();
5035 auto takerEURBalance = env.balance(taker, EUR).value();
5036 auto makerXRPBalance = env.balance(maker, XRP).value();
5037 auto takerXRPBalance = env.balance(taker, XRP).value();
5038
5039 // tfFillOrKill, TakerPays must be filled
5040 {
5041 TER const err =
5042 features[fixFillOrKill] ? TER(tesSUCCESS) : tecKILLED;
5043
5044 env(offer(maker, XRP(100), USD(100)));
5045 env.close();
5046
5047 env(offer(taker, USD(100), XRP(101)),
5049 ter(err));
5050 env.close();
5051
5052 makerXRPBalance -= txfee(env, 1);
5053 takerXRPBalance -= txfee(env, 1);
5054 if (err == tesSUCCESS)
5055 {
5056 makerUSDBalance -= USD(100);
5057 takerUSDBalance += USD(100);
5058 makerXRPBalance += XRP(100).value();
5059 takerXRPBalance -= XRP(100).value();
5060 }
5061 BEAST_EXPECT(expectOffers(env, taker, 0));
5062
5063 env(offer(maker, USD(100), XRP(100)));
5064 env.close();
5065
5066 env(offer(taker, XRP(100), USD(101)),
5068 ter(err));
5069 env.close();
5070
5071 makerXRPBalance -= txfee(env, 1);
5072 takerXRPBalance -= txfee(env, 1);
5073 if (err == tesSUCCESS)
5074 {
5075 makerUSDBalance += USD(100);
5076 takerUSDBalance -= USD(100);
5077 makerXRPBalance -= XRP(100).value();
5078 takerXRPBalance += XRP(100).value();
5079 }
5080 BEAST_EXPECT(expectOffers(env, taker, 0));
5081
5082 env(offer(maker, USD(100), EUR(100)));
5083 env.close();
5084
5085 env(offer(taker, EUR(100), USD(101)),
5087 ter(err));
5088 env.close();
5089
5090 makerXRPBalance -= txfee(env, 1);
5091 takerXRPBalance -= txfee(env, 1);
5092 if (err == tesSUCCESS)
5093 {
5094 makerUSDBalance += USD(100);
5095 takerUSDBalance -= USD(100);
5096 makerEURBalance -= EUR(100);
5097 takerEURBalance += EUR(100);
5098 }
5099 BEAST_EXPECT(expectOffers(env, taker, 0));
5100 }
5101
5102 // tfFillOrKill + tfSell, TakerGets must be filled
5103 {
5104 env(offer(maker, XRP(101), USD(101)));
5105 env.close();
5106
5107 env(offer(taker, USD(100), XRP(101)),
5109 env.close();
5110
5111 makerUSDBalance -= USD(101);
5112 takerUSDBalance += USD(101);
5113 makerXRPBalance += XRP(101).value() - txfee(env, 1);
5114 takerXRPBalance -= XRP(101).value() + txfee(env, 1);
5115 BEAST_EXPECT(expectOffers(env, taker, 0));
5116
5117 env(offer(maker, USD(101), XRP(101)));
5118 env.close();
5119
5120 env(offer(taker, XRP(100), USD(101)),
5122 env.close();
5123
5124 makerUSDBalance += USD(101);
5125 takerUSDBalance -= USD(101);
5126 makerXRPBalance -= XRP(101).value() + txfee(env, 1);
5127 takerXRPBalance += XRP(101).value() - txfee(env, 1);
5128 BEAST_EXPECT(expectOffers(env, taker, 0));
5129
5130 env(offer(maker, USD(101), EUR(101)));
5131 env.close();
5132
5133 env(offer(taker, EUR(100), USD(101)),
5135 env.close();
5136
5137 makerUSDBalance += USD(101);
5138 takerUSDBalance -= USD(101);
5139 makerEURBalance -= EUR(101);
5140 takerEURBalance += EUR(101);
5141 makerXRPBalance -= txfee(env, 1);
5142 takerXRPBalance -= txfee(env, 1);
5143 BEAST_EXPECT(expectOffers(env, taker, 0));
5144 }
5145
5146 // Fail regardless of fixFillOrKill amendment
5147 for (auto const flags : {tfFillOrKill, tfFillOrKill + tfSell})
5148 {
5149 env(offer(maker, XRP(100), USD(100)));
5150 env.close();
5151
5152 env(offer(taker, USD(100), XRP(99)),
5153 txflags(flags),
5154 ter(tecKILLED));
5155 env.close();
5156
5157 makerXRPBalance -= txfee(env, 1);
5158 takerXRPBalance -= txfee(env, 1);
5159 BEAST_EXPECT(expectOffers(env, taker, 0));
5160
5161 env(offer(maker, USD(100), XRP(100)));
5162 env.close();
5163
5164 env(offer(taker, XRP(100), USD(99)),
5165 txflags(flags),
5166 ter(tecKILLED));
5167 env.close();
5168
5169 makerXRPBalance -= txfee(env, 1);
5170 takerXRPBalance -= txfee(env, 1);
5171 BEAST_EXPECT(expectOffers(env, taker, 0));
5172
5173 env(offer(maker, USD(100), EUR(100)));
5174 env.close();
5175
5176 env(offer(taker, EUR(100), USD(99)),
5177 txflags(flags),
5178 ter(tecKILLED));
5179 env.close();
5180
5181 makerXRPBalance -= txfee(env, 1);
5182 takerXRPBalance -= txfee(env, 1);
5183 BEAST_EXPECT(expectOffers(env, taker, 0));
5184 }
5185
5186 BEAST_EXPECT(
5187 env.balance(maker, USD) == makerUSDBalance &&
5188 env.balance(taker, USD) == takerUSDBalance &&
5189 env.balance(maker, EUR) == makerEURBalance &&
5190 env.balance(taker, EUR) == takerEURBalance &&
5191 env.balance(maker, XRP) == makerXRPBalance &&
5192 env.balance(taker, XRP) == takerXRPBalance);
5193 }
5194
5195 void
5197 {
5198 testCanceledOffer(features);
5199 testRmFundedOffer(features);
5200 testTinyPayment(features);
5201 testXRPTinyPayment(features);
5202 testEnforceNoRipple(features);
5203 testInsufficientReserve(features);
5204 testFillModes(features);
5205 testMalformed(features);
5206 testExpiration(features);
5207 testUnfundedCross(features);
5208 testSelfCross(false, features);
5209 testSelfCross(true, features);
5210 testNegativeBalance(features);
5211 testOfferCrossWithXRP(true, features);
5212 testOfferCrossWithXRP(false, features);
5214 testOfferAcceptThenCancel(features);
5219 testCrossCurrencyStartXRP(features);
5220 testCrossCurrencyEndXRP(features);
5221 testCrossCurrencyBridged(features);
5222 testBridgedSecondLegDry(features);
5223 testOfferFeesConsumeFunds(features);
5224 testOfferCreateThenCross(features);
5225 testSellFlagBasic(features);
5226 testSellFlagExceedLimit(features);
5227 testGatewayCrossCurrency(features);
5228 testPartialCross(features);
5229 testXRPDirectCross(features);
5230 testDirectCross(features);
5231 testBridgedCross(features);
5232 testSellOffer(features);
5233 testSellWithFillOrKill(features);
5234 testTransferRateOffer(features);
5235 testSelfCrossOffer(features);
5236 testSelfIssueOffer(features);
5237 testBadPathAssert(features);
5238 testDirectToDirectPath(features);
5240 testOfferInScaling(features);
5243 testTinyOffer(features);
5244 testSelfPayXferFeeOffer(features);
5245 testSelfPayUnlimitedFunds(features);
5246 testRequireAuth(features);
5247 testMissingAuth(features);
5248 testRCSmoketest(features);
5249 testSelfAuth(features);
5250 testDeletedOfferIssuer(features);
5251 testTickSize(features);
5252 testTicketOffer(features);
5253 testTicketCancelOffer(features);
5256 testFillOrKill(features);
5257 }
5258
5260
5261 void
5262 run() override
5263 {
5264 testAll(allFeatures - featurePermissionedDEX);
5266 }
5267};
5268
5270{
5271 void
5272 run() override
5273 {
5274 testAll(allFeatures - fixFillOrKill - featurePermissionedDEX);
5275 }
5276};
5277
5279{
5280 void
5281 run() override
5282 {
5284 }
5285};
5286
5288{
5289 void
5290 run() override
5291 {
5292 using namespace jtx;
5294 FeatureBitset const fillOrKill{fixFillOrKill};
5295 FeatureBitset const permDEX{featurePermissionedDEX};
5296
5297 testAll(all - fillOrKill - permDEX);
5298 testAll(all - permDEX);
5299 testAll(all);
5300 }
5301};
5302
5303BEAST_DEFINE_TESTSUITE_PRIO(OfferBaseUtil, app, xrpl, 2);
5304BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, app, xrpl, 2);
5305BEAST_DEFINE_TESTSUITE_PRIO(OfferAllFeatures, app, xrpl, 2);
5306BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Offer_manual, app, xrpl, 20);
5307
5308} // namespace test
5309} // namespace xrpl
Represents a JSON value.
Definition json_value.h:131
A testsuite class.
Definition suite.h:52
void pass()
Record a successful test condition.
Definition suite.h:508
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
A currency issued by an account.
Definition Issue.h:14
Currency currency
Definition Issue.h:16
AccountID account
Definition Issue.h:17
Issue const & issue() const
Definition STAmount.h:488
void negate()
Definition STAmount.h:556
std::string getText() const override
Definition STAmount.cpp:663
void run() override
Runs the suite.
static std::vector< std::shared_ptr< SLE const > > sortedOffersOnAccount(jtx::Env &env, jtx::Account const &acct)
void testRequireAuth(FeatureBitset features)
void testCrossCurrencyEndXRP(FeatureBitset features)
void testXRPDirectCross(FeatureBitset features)
void testNegativeBalance(FeatureBitset features)
void testGatewayCrossCurrency(FeatureBitset features)
void testXRPTinyPayment(FeatureBitset features)
void testTicketOffer(FeatureBitset features)
void testCurrencyConversionIntoDebt(FeatureBitset features)
void testCanceledOffer(FeatureBitset features)
void testDirectToDirectPath(FeatureBitset features)
void testMissingAuth(FeatureBitset features)
void run() override
Runs the suite.
XRPAmount reserve(jtx::Env &env, std::uint32_t count)
void testCrossCurrencyStartXRP(FeatureBitset features)
void testRmFundedOffer(FeatureBitset features)
static std::vector< std::shared_ptr< SLE const > > offersOnAccount(jtx::Env &env, jtx::Account account)
void testSelfCrossOffer1(FeatureBitset features)
void testSellWithFillOrKill(FeatureBitset features)
void testRCSmoketest(FeatureBitset features)
void testAll(FeatureBitset features)
void testSellOffer(FeatureBitset features)
void testOfferFeesConsumeFunds(FeatureBitset features)
void testSellFlagBasic(FeatureBitset features)
void testTinyOffer(FeatureBitset features)
void testSelfCrossOffer(FeatureBitset features)
void testSelfCrossLowQualityOffer(FeatureBitset features)
void testRmSmallIncreasedQOffersXRP(FeatureBitset features)
void verifyDefaultTrustline(jtx::Env &env, jtx::Account const &account, jtx::PrettyAmount const &expectBalance)
void testFillModes(FeatureBitset features)
void testTicketCancelOffer(FeatureBitset features)
void testTickSize(FeatureBitset features)
static auto getBookOffers(jtx::Env &env, Issue const &taker_pays, Issue const &taker_gets)
void testOfferAcceptThenCancel(FeatureBitset features)
void testExpiration(FeatureBitset features)
void testRmSmallIncreasedQOffersIOU(FeatureBitset features)
void testSelfPayUnlimitedFunds(FeatureBitset features)
void testMalformed(FeatureBitset features)
void testDeletedOfferIssuer(FeatureBitset features)
void testOfferCreateThenCross(FeatureBitset features)
void testTinyPayment(FeatureBitset features)
void testSellFlagExceedLimit(FeatureBitset features)
void testTransferRateOffer(FeatureBitset features)
void testOfferThresholdWithReducedFunds(FeatureBitset features)
void testOfferCrossWithXRP(bool reverse_order, FeatureBitset features)
std::uint32_t lastClose(jtx::Env &env)
void testInsufficientReserve(FeatureBitset features)
void testCrossCurrencyBridged(FeatureBitset features)
void testUnfundedCross(FeatureBitset features)
void testBridgedSecondLegDry(FeatureBitset features)
void testBadPathAssert(FeatureBitset features)
void testOfferInScalingWithXferRate(FeatureBitset features)
void testOfferCrossWithLimitOverride(FeatureBitset features)
void testSelfPayXferFeeOffer(FeatureBitset features)
void testSelfAuth(FeatureBitset features)
FeatureBitset const allFeatures
void testSelfIssueOffer(FeatureBitset features)
void testPartialCross(FeatureBitset features)
void testBridgedCross(FeatureBitset features)
void testCurrencyConversionEntire(FeatureBitset features)
void testCurrencyConversionInParts(FeatureBitset features)
void testSelfCrossOffer2(FeatureBitset features)
void testOfferInScaling(FeatureBitset features)
static auto ledgerEntryOffer(jtx::Env &env, jtx::Account const &acct, std::uint32_t offer_seq)
void testOfferCancelPastAndFuture(FeatureBitset features)
void testFillOrKill(FeatureBitset features)
void testDirectCross(FeatureBitset features)
void testSelfCross(bool use_partner, FeatureBitset features)
void testEnforceNoRipple(FeatureBitset features)
void run() override
Runs the suite.
void run() override
Runs the suite.
Immutable cryptographic account descriptor.
Definition Account.h:20
std::string const & human() const
Returns the human readable public key.
Definition Account.h:99
AccountID id() const
Returns the Account ID.
Definition Account.h:92
A transaction testing environment.
Definition Env.h:102
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:104
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:98
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:260
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:272
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:251
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:166
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:303
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:774
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:530
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:314
Converts to IOU Issue or STAmount.
A balance matches.
Definition balance.h:20
Set the fee on a JTx.
Definition fee.h:18
Match set account flags.
Definition flags.h:109
Inject raw JSON.
Definition jtx_json.h:14
Match the number of items in the account's owner directory.
Definition owners.h:54
Add a path.
Definition paths.h:39
Set Paths, SendMax on a JTx.
Definition paths.h:16
Sets the QualityIn on a trust JTx.
Definition quality.h:27
Sets the QualityOut on a trust JTx as a percentage.
Definition quality.h:55
Check a set of conditions.
Definition require.h:47
Sets the SendMax on a JTx.
Definition sendmax.h:14
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:16
Set a ticket sequence on a JTx.
Definition ticket.h:29
Set the flags on a JTx.
Definition txflags.h:12
T distance(T... args)
@ arrayValue
array value (ordered list)
Definition json_value.h:26
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:226
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
PrettyAmount xrpMinusFee(Env const &env, std::int64_t xrpAmount)
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
Json::Value ledgerEntryState(Env &env, Account const &acct_a, Account const &acct_b, std::string const &currency)
static epsilon_t const epsilon
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition rate.cpp:13
XRPAmount txfee(Env const &env, std::uint16_t n)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
Json::Value ledgerEntryRoot(Env &env, Account const &acct)
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:27
static none_t const none
Definition tags.h:15
FeatureBitset testable_amendments()
Definition Env.h:55
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition owners.h:73
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
owner_count< ltTICKET > tickets
Match the number of tickets on the account.
Definition ticket.h:45
owner_count< ltRIPPLE_STATE > lines
Match the number of trust lines in the account's owner directory.
Definition owners.h:70
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition WSClient.cpp:304
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
Definition PathSet.h:53
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t tfSell
Definition TxFlags.h:82
constexpr std::uint32_t tfPassive
Definition TxFlags.h:79
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
constexpr std::uint32_t tfImmediateOrCancel
Definition TxFlags.h:80
constexpr std::uint32_t tfFillOrKill
Definition TxFlags.h:81
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t tfSetNoRipple
Definition TxFlags.h:97
void forEachItem(ReadView const &view, Keylet const &root, std::function< void(std::shared_ptr< SLE const > const &)> const &f)
Iterate all items in the given directory.
Definition View.cpp:759
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:57
constexpr std::uint32_t asfDefaultRipple
Definition TxFlags.h:65
TERSubset< CanCvtToTER > TER
Definition TER.h:630
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:88
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
@ temBAD_PATH
Definition TER.h:77
@ temBAD_CURRENCY
Definition TER.h:71
@ temBAD_EXPIRATION
Definition TER.h:72
@ temBAD_SEQUENCE
Definition TER.h:85
@ temINVALID_FLAG
Definition TER.h:92
@ temBAD_TICK_SIZE
Definition TER.h:99
@ temBAD_OFFER
Definition TER.h:76
@ temREDUNDANT
Definition TER.h:93
@ tecINSUF_RESERVE_OFFER
Definition TER.h:271
@ tecPATH_PARTIAL
Definition TER.h:264
@ tecPATH_DRY
Definition TER.h:276
@ tecNO_AUTH
Definition TER.h:282
@ tecUNFUNDED_OFFER
Definition TER.h:266
@ tecEXPIRED
Definition TER.h:296
@ tecNO_LINE
Definition TER.h:283
@ tecOWNERS
Definition TER.h:280
@ tecKILLED
Definition TER.h:298
@ tecNO_ISSUER
Definition TER.h:281
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:89
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
@ tesSUCCESS
Definition TER.h:226
T remove_if(T... args)
T sort(T... args)
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...
STAmount const & value() const
T to_string(T... args)