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