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