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