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