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