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