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