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