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