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