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