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