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