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