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