rippled
Loading...
Searching...
No Matches
ReducedOffer_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2022 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 <test/jtx.h>
21
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/Quality.h>
24#include <xrpl/protocol/jss.h>
25
26#include <initializer_list>
27
28namespace ripple {
29namespace test {
30
32{
33 static auto
35 jtx::Env& env,
36 jtx::Account const& acct,
37 std::uint32_t offer_seq)
38 {
39 Json::Value jvParams;
40 jvParams[jss::offer][jss::account] = acct.human();
41 jvParams[jss::offer][jss::seq] = offer_seq;
42 return env.rpc(
43 "json", "ledger_entry", to_string(jvParams))[jss::result];
44 }
45
46 static bool
48 jtx::Env& env,
49 jtx::Account const& acct,
50 std::uint32_t offerSeq)
51 {
52 Json::Value ledgerOffer = ledgerEntryOffer(env, acct, offerSeq);
53 return !(
54 ledgerOffer.isMember(jss::error) &&
55 ledgerOffer[jss::error].asString() == "entryNotFound");
56 }
57
58 // Common code to clean up unneeded offers.
59 static void
61 jtx::Env& env,
63 list)
64 {
65 for (auto [acct, offerSeq] : list)
66 env(offer_cancel(acct, offerSeq));
67 env.close();
68 }
69
70public:
71 void
73 {
74 testcase("exercise partial cross new XRP/IOU offer Q change");
75
76 using namespace jtx;
77
78 auto const gw = Account{"gateway"};
79 auto const alice = Account{"alice"};
80 auto const bob = Account{"bob"};
81 auto const USD = gw["USD"];
82
83 {
84 Env env{*this, testable_amendments()};
85
86 // Make sure none of the offers we generate are under funded.
87 env.fund(XRP(10'000'000), gw, alice, bob);
88 env.close();
89
90 env(trust(alice, USD(10'000'000)));
91 env(trust(bob, USD(10'000'000)));
92 env.close();
93
94 env(pay(gw, bob, USD(10'000'000)));
95 env.close();
96
97 // Lambda that:
98 // 1. Exercises one offer pair,
99 // 2. Collects the results, and
100 // 3. Cleans up for the next offer pair.
101 // Returns 1 if the crossed offer has a bad rate for the book.
102 auto exerciseOfferPair =
103 [this, &env, &alice, &bob](
104 Amounts const& inLedger,
105 Amounts const& newOffer) -> unsigned int {
106 // Put inLedger offer in the ledger so newOffer can cross it.
107 std::uint32_t const aliceOfferSeq = env.seq(alice);
108 env(offer(alice, inLedger.in, inLedger.out));
109 env.close();
110
111 // Now alice's offer will partially cross bob's offer.
112 STAmount const initialRate = Quality(newOffer).rate();
113 std::uint32_t const bobOfferSeq = env.seq(bob);
114 STAmount const bobInitialBalance = env.balance(bob);
115 STAmount const bobsFee = env.current()->fees().base;
116 env(offer(bob, newOffer.in, newOffer.out, tfSell),
117 fee(bobsFee));
118 env.close();
119 STAmount const bobFinalBalance = env.balance(bob);
120
121 // alice's offer should be fully crossed and so gone from
122 // the ledger.
123 if (!BEAST_EXPECT(!offerInLedger(env, alice, aliceOfferSeq)))
124 // If the in-ledger offer was not consumed then further
125 // results are meaningless.
126 return 1;
127
128 // bob's offer should be in the ledger, but reduced in size.
129 unsigned int badRate = 1;
130 {
131 Json::Value bobOffer =
132 ledgerEntryOffer(env, bob, bobOfferSeq);
133
134 STAmount const reducedTakerGets = amountFromJson(
135 sfTakerGets, bobOffer[jss::node][sfTakerGets.jsonName]);
136 STAmount const reducedTakerPays = amountFromJson(
137 sfTakerPays, bobOffer[jss::node][sfTakerPays.jsonName]);
138 STAmount const bobGot =
139 env.balance(bob) + bobsFee - bobInitialBalance;
140 BEAST_EXPECT(reducedTakerPays < newOffer.in);
141 BEAST_EXPECT(reducedTakerGets < newOffer.out);
142 STAmount const inLedgerRate =
143 Quality(Amounts{reducedTakerPays, reducedTakerGets})
144 .rate();
145
146 badRate = inLedgerRate > initialRate ? 1 : 0;
147
148 // If the inLedgerRate is less than initial rate, then
149 // incrementing the mantissa of the reduced taker pays
150 // should result in a rate higher than initial. Check
151 // this to verify that the largest allowable TakerPays
152 // was computed.
153 if (badRate == 0)
154 {
155 STAmount const tweakedTakerPays =
156 reducedTakerPays + drops(1);
157 STAmount const tweakedRate =
158 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
159 .rate();
160 BEAST_EXPECT(tweakedRate > initialRate);
161 }
162#if 0
163 std::cout << "Placed rate: " << initialRate
164 << "; in-ledger rate: " << inLedgerRate
165 << "; TakerPays: " << reducedTakerPays
166 << "; TakerGets: " << reducedTakerGets
167 << "; bob already got: " << bobGot << std::endl;
168// #else
169 std::string_view filler =
170 inLedgerRate > initialRate ? "**" : " ";
171 std::cout << "| `" << reducedTakerGets << "` | `"
172 << reducedTakerPays << "` | `" << initialRate
173 << "` | " << filler << "`" << inLedgerRate << "`"
174 << filler << " |`" << std::endl;
175#endif
176 }
177
178 // In preparation for the next iteration make sure the two
179 // offers are gone from the ledger.
181 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
182 return badRate;
183 };
184
185 // bob's offer (the new offer) is the same every time:
186 Amounts const bobsOffer{
187 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
188
189 // alice's offer has a slightly smaller TakerPays with each
190 // iteration. This should mean that the size of the offer bob
191 // places in the ledger should increase with each iteration.
192 unsigned int blockedCount = 0;
193 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
194 mantissaReduce <= 5'000'000'000ull;
195 mantissaReduce += 20'000'000ull)
196 {
197 STAmount aliceUSD{
198 bobsOffer.out.issue(),
199 bobsOffer.out.mantissa() - mantissaReduce,
200 bobsOffer.out.exponent()};
201 STAmount aliceXRP{
202 bobsOffer.in.issue(), bobsOffer.in.mantissa() - 1};
203 Amounts alicesOffer{aliceUSD, aliceXRP};
204 blockedCount += exerciseOfferPair(alicesOffer, bobsOffer);
205 }
206
207 // None of the test cases should produce a potentially blocking
208 // rate.
209 BEAST_EXPECT(blockedCount == 0);
210 }
211 }
212
213 void
215 {
216 testcase("exercise partial cross old XRP/IOU offer Q change");
217
218 using namespace jtx;
219
220 auto const gw = Account{"gateway"};
221 auto const alice = Account{"alice"};
222 auto const bob = Account{"bob"};
223 auto const USD = gw["USD"];
224
225 {
226 // Make sure none of the offers we generate are under funded.
227 Env env{*this, testable_amendments()};
228 env.fund(XRP(10'000'000), gw, alice, bob);
229 env.close();
230
231 env(trust(alice, USD(10'000'000)));
232 env(trust(bob, USD(10'000'000)));
233 env.close();
234
235 env(pay(gw, alice, USD(10'000'000)));
236 env.close();
237
238 // Lambda that:
239 // 1. Exercises one offer pair,
240 // 2. Collects the results, and
241 // 3. Cleans up for the next offer pair.
242 auto exerciseOfferPair =
243 [this, &env, &alice, &bob](
244 Amounts const& inLedger,
245 Amounts const& newOffer) -> unsigned int {
246 // Get the inLedger offer into the ledger so newOffer can cross
247 // it.
248 STAmount const initialRate = Quality(inLedger).rate();
249 std::uint32_t const aliceOfferSeq = env.seq(alice);
250 env(offer(alice, inLedger.in, inLedger.out));
251 env.close();
252
253 // Now bob's offer will partially cross alice's offer.
254 std::uint32_t const bobOfferSeq = env.seq(bob);
255 STAmount const aliceInitialBalance = env.balance(alice);
256 env(offer(bob, newOffer.in, newOffer.out));
257 env.close();
258 STAmount const aliceFinalBalance = env.balance(alice);
259
260 // bob's offer should not have made it into the ledger.
261 if (!BEAST_EXPECT(!offerInLedger(env, bob, bobOfferSeq)))
262 {
263 // If the in-ledger offer was not consumed then further
264 // results are meaningless.
266 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
267 return 1;
268 }
269 // alice's offer should still be in the ledger, but reduced in
270 // size.
271 unsigned int badRate = 1;
272 {
273 Json::Value aliceOffer =
274 ledgerEntryOffer(env, alice, aliceOfferSeq);
275
276 STAmount const reducedTakerGets = amountFromJson(
277 sfTakerGets,
278 aliceOffer[jss::node][sfTakerGets.jsonName]);
279 STAmount const reducedTakerPays = amountFromJson(
280 sfTakerPays,
281 aliceOffer[jss::node][sfTakerPays.jsonName]);
282 STAmount const aliceGot =
283 env.balance(alice) - aliceInitialBalance;
284 BEAST_EXPECT(reducedTakerPays < inLedger.in);
285 BEAST_EXPECT(reducedTakerGets < inLedger.out);
286 STAmount const inLedgerRate =
287 Quality(Amounts{reducedTakerPays, reducedTakerGets})
288 .rate();
289 badRate = inLedgerRate > initialRate ? 1 : 0;
290
291 // If the inLedgerRate is less than initial rate, then
292 // incrementing the mantissa of the reduced taker pays
293 // should result in a rate higher than initial. Check
294 // this to verify that the largest allowable TakerPays
295 // was computed.
296 if (badRate == 0)
297 {
298 STAmount const tweakedTakerPays =
299 reducedTakerPays + drops(1);
300 STAmount const tweakedRate =
301 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
302 .rate();
303 BEAST_EXPECT(tweakedRate > initialRate);
304 }
305#if 0
306 std::cout << "Placed rate: " << initialRate
307 << "; in-ledger rate: " << inLedgerRate
308 << "; TakerPays: " << reducedTakerPays
309 << "; TakerGets: " << reducedTakerGets
310 << "; alice already got: " << aliceGot
311 << std::endl;
312// #else
313 std::string_view filler = badRate ? "**" : " ";
314 std::cout << "| `" << reducedTakerGets << "` | `"
315 << reducedTakerPays << "` | `" << initialRate
316 << "` | " << filler << "`" << inLedgerRate << "`"
317 << filler << " | `" << aliceGot << "` |"
318 << std::endl;
319#endif
320 }
321
322 // In preparation for the next iteration make sure the two
323 // offers are gone from the ledger.
325 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
326 return badRate;
327 };
328
329 // alice's offer (the old offer) is the same every time:
330 Amounts const aliceOffer{
331 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
332
333 // bob's offer has a slightly smaller TakerPays with each iteration.
334 // This should mean that the size of the offer alice leaves in the
335 // ledger should increase with each iteration.
336 unsigned int blockedCount = 0;
337 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
338 mantissaReduce <= 4'000'000'000ull;
339 mantissaReduce += 20'000'000ull)
340 {
341 STAmount bobUSD{
342 aliceOffer.out.issue(),
343 aliceOffer.out.mantissa() - mantissaReduce,
344 aliceOffer.out.exponent()};
345 STAmount bobXRP{
346 aliceOffer.in.issue(), aliceOffer.in.mantissa() - 1};
347 Amounts bobsOffer{bobUSD, bobXRP};
348
349 blockedCount += exerciseOfferPair(aliceOffer, bobsOffer);
350 }
351
352 // None of the test cases should produce a potentially blocking
353 // rate.
354 BEAST_EXPECT(blockedCount == 0);
355 }
356 }
357
358 void
360 {
361 testcase("exercise underfunded XRP/IOU offer Q change");
362
363 // Bob places an offer that is not fully funded.
364
365 using namespace jtx;
366 auto const alice = Account{"alice"};
367 auto const bob = Account{"bob"};
368 auto const gw = Account{"gw"};
369 auto const USD = gw["USD"];
370
371 {
372 Env env{*this, testable_amendments()};
373
374 env.fund(XRP(10000), alice, bob, gw);
375 env.close();
376 env.trust(USD(1000), alice, bob);
377
378 int blockedOrderBookCount = 0;
379 for (STAmount initialBobUSD = USD(0.45); initialBobUSD <= USD(1);
380 initialBobUSD += USD(0.025))
381 {
382 // underfund bob's offer
383 env(pay(gw, bob, initialBobUSD));
384 env.close();
385
386 std::uint32_t const bobOfferSeq = env.seq(bob);
387 env(offer(bob, drops(2), USD(1)));
388 env.close();
389
390 // alice places an offer that would cross bob's if bob's were
391 // well funded.
392 std::uint32_t const aliceOfferSeq = env.seq(alice);
393 env(offer(alice, USD(1), drops(2)));
394 env.close();
395
396 // We want to detect order book blocking. If:
397 // 1. bob's offer is still in the ledger and
398 // 2. alice received no USD
399 // then we use that as evidence that bob's offer blocked the
400 // order book.
401 {
402 bool const bobsOfferGone =
403 !offerInLedger(env, bob, bobOfferSeq);
404 STAmount const aliceBalanceUSD = env.balance(alice, USD);
405
406 // Sanity check the ledger if alice got USD.
407 if (aliceBalanceUSD.signum() > 0)
408 {
409 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
410 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
411 BEAST_EXPECT(bobsOfferGone);
412 }
413
414 // Track occurrences of order book blocking.
415 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
416 {
417 ++blockedOrderBookCount;
418 }
419
420 // In preparation for the next iteration clean up any
421 // leftover offers.
423 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
424
425 // Zero out alice's and bob's USD balances.
426 if (STAmount const aliceBalance = env.balance(alice, USD);
427 aliceBalance.signum() > 0)
428 env(pay(alice, gw, aliceBalance));
429
430 if (STAmount const bobBalance = env.balance(bob, USD);
431 bobBalance.signum() > 0)
432 env(pay(bob, gw, bobBalance));
433
434 env.close();
435 }
436 }
437
438 // None of the test cases should produce a potentially blocking
439 // rate.
440 BEAST_EXPECT(blockedOrderBookCount == 0);
441 }
442 }
443
444 void
446 {
447 testcase("exercise underfunded IOU/IOU offer Q change");
448
449 // Bob places an IOU/IOU offer that is not fully funded.
450
451 using namespace jtx;
452 using namespace std::chrono_literals;
453 auto const alice = Account{"alice"};
454 auto const bob = Account{"bob"};
455 auto const gw = Account{"gw"};
456
457 auto const USD = gw["USD"];
458 auto const EUR = gw["EUR"];
459
460 STAmount const tinyUSD(USD.issue(), /*mantissa*/ 1, /*exponent*/ -81);
461
462 {
463 Env env{*this, testable_amendments()};
464
465 env.fund(XRP(10000), alice, bob, gw);
466 env.close();
467 env.trust(USD(1000), alice, bob);
468 env.trust(EUR(1000), alice, bob);
469
470 STAmount const eurOffer(
471 EUR.issue(), /*mantissa*/ 2957, /*exponent*/ -76);
472 STAmount const usdOffer(
473 USD.issue(), /*mantissa*/ 7109, /*exponent*/ -76);
474
475 STAmount const endLoop(
476 USD.issue(), /*mantissa*/ 50, /*exponent*/ -81);
477
478 int blockedOrderBookCount = 0;
479 for (STAmount initialBobUSD = tinyUSD; initialBobUSD <= endLoop;
480 initialBobUSD += tinyUSD)
481 {
482 // underfund bob's offer
483 env(pay(gw, bob, initialBobUSD));
484 env(pay(gw, alice, EUR(100)));
485 env.close();
486
487 // This offer is underfunded
488 std::uint32_t bobOfferSeq = env.seq(bob);
489 env(offer(bob, eurOffer, usdOffer));
490 env.close();
491 env.require(offers(bob, 1));
492
493 // alice places an offer that crosses bob's.
494 std::uint32_t aliceOfferSeq = env.seq(alice);
495 env(offer(alice, usdOffer, eurOffer));
496 env.close();
497
498 // Examine the aftermath of alice's offer.
499 {
500 bool const bobsOfferGone =
501 !offerInLedger(env, bob, bobOfferSeq);
502 STAmount aliceBalanceUSD = env.balance(alice, USD);
503#if 0
505 << "bobs initial: " << initialBobUSD
506 << "; alice final: " << aliceBalanceUSD
507 << "; bobs offer: " << bobsOfferJson.toStyledString()
508 << std::endl;
509#endif
510 // Sanity check the ledger if alice got USD.
511 if (aliceBalanceUSD.signum() > 0)
512 {
513 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
514 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
515 BEAST_EXPECT(bobsOfferGone);
516 }
517
518 // Track occurrences of order book blocking.
519 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
520 {
521 ++blockedOrderBookCount;
522 }
523 }
524
525 // In preparation for the next iteration clean up any
526 // leftover offers.
528 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
529
530 // Zero out alice's and bob's IOU balances.
531 auto zeroBalance = [&env, &gw](
532 Account const& acct, IOU const& iou) {
533 if (STAmount const balance = env.balance(acct, iou);
534 balance.signum() > 0)
535 env(pay(acct, gw, balance));
536 };
537
538 zeroBalance(alice, EUR);
539 zeroBalance(alice, USD);
540 zeroBalance(bob, EUR);
541 zeroBalance(bob, USD);
542 env.close();
543 }
544
545 // None of the test cases should produce a potentially blocking
546 // rate.
547 BEAST_EXPECT(blockedOrderBookCount == 0);
548 }
549 }
550
551 Amounts
553 {
554 STAmount const in =
555 amountFromJson(sfTakerPays, json[sfTakerPays.jsonName]);
556 STAmount const out =
557 amountFromJson(sfTakerGets, json[sfTakerGets.jsonName]);
558 return {in, out};
559 }
560
561 void
563 {
564 // This test case was motivated by Issue #4937. It recreates
565 // the specific failure identified in that issue and samples some other
566 // cases in the same vicinity to make sure that the new behavior makes
567 // sense.
568 testcase("exercise tfSell partial cross old XRP/IOU offer Q change");
569
570 using namespace jtx;
571
572 Account const gw("gateway");
573 Account const alice("alice");
574 Account const bob("bob");
575 Account const carol("carol");
576 auto const USD = gw["USD"];
577
578 // Make one test run without fixReducedOffersV2 and one with.
579 for (FeatureBitset features :
580 {testable_amendments() - fixReducedOffersV2,
581 testable_amendments() | fixReducedOffersV2})
582 {
583 // Make sure none of the offers we generate are under funded.
584 Env env{*this, features};
585 env.fund(XRP(10'000'000), gw, alice, bob, carol);
586 env.close();
587
588 env(trust(alice, USD(10'000'000)));
589 env(trust(bob, USD(10'000'000)));
590 env(trust(carol, USD(10'000'000)));
591 env.close();
592
593 env(pay(gw, alice, USD(10'000'000)));
594 env(pay(gw, bob, USD(10'000'000)));
595 env(pay(gw, carol, USD(10'000'000)));
596 env.close();
597
598 // Lambda that:
599 // 1. Exercises one offer trio,
600 // 2. Collects the results, and
601 // 3. Cleans up for the next offer trio.
602 auto exerciseOfferTrio =
603 [this, &env, &alice, &bob, &carol, &USD](
604 Amounts const& carolOffer) -> unsigned int {
605 // alice submits an offer that may become a blocker.
606 std::uint32_t const aliceOfferSeq = env.seq(alice);
607 static Amounts const aliceInitialOffer(USD(2), drops(3382562));
608 env(offer(alice, aliceInitialOffer.in, aliceInitialOffer.out));
609 env.close();
610 STAmount const initialRate =
612 env, alice, aliceOfferSeq)[jss::node]))
613 .rate();
614
615 // bob submits an offer that is more desirable than alice's
616 std::uint32_t const bobOfferSeq = env.seq(bob);
617 env(offer(bob, USD(0.97086565812384), drops(1642020)));
618 env.close();
619
620 // Now carol's offer consumes bob's and partially crosses
621 // alice's. The tfSell flag is important.
622 std::uint32_t const carolOfferSeq = env.seq(carol);
623 env(offer(carol, carolOffer.in, carolOffer.out),
624 txflags(tfSell));
625 env.close();
626
627 // carol's offer should not have made it into the ledger and
628 // bob's offer should be fully consumed.
629 if (!BEAST_EXPECT(
630 !offerInLedger(env, carol, carolOfferSeq) &&
631 !offerInLedger(env, bob, bobOfferSeq)))
632 {
633 // If carol's or bob's offers are still in the ledger then
634 // further results are meaningless.
636 env,
637 {{alice, aliceOfferSeq},
638 {bob, bobOfferSeq},
639 {carol, carolOfferSeq}});
640 return 1;
641 }
642 // alice's offer should still be in the ledger, but reduced in
643 // size.
644 unsigned int badRate = 1;
645 {
646 Json::Value aliceOffer =
647 ledgerEntryOffer(env, alice, aliceOfferSeq);
648
649 Amounts aliceReducedOffer =
650 jsonOfferToAmounts(aliceOffer[jss::node]);
651
652 BEAST_EXPECT(aliceReducedOffer.in < aliceInitialOffer.in);
653 BEAST_EXPECT(aliceReducedOffer.out < aliceInitialOffer.out);
654 STAmount const inLedgerRate =
655 Quality(aliceReducedOffer).rate();
656 badRate = inLedgerRate > initialRate ? 1 : 0;
657
658 // If the inLedgerRate is less than initial rate, then
659 // incrementing the mantissa of the reduced TakerGets
660 // should result in a rate higher than initial. Check
661 // this to verify that the largest allowable TakerGets
662 // was computed.
663 if (badRate == 0)
664 {
665 STAmount const tweakedTakerGets(
666 aliceReducedOffer.in.issue(),
667 aliceReducedOffer.in.mantissa() + 1,
668 aliceReducedOffer.in.exponent(),
669 aliceReducedOffer.in.negative());
670 STAmount const tweakedRate =
671 Quality(
672 Amounts{aliceReducedOffer.in, tweakedTakerGets})
673 .rate();
674 BEAST_EXPECT(tweakedRate > initialRate);
675 }
676#if 0
677 std::cout << "Placed rate: " << initialRate
678 << "; in-ledger rate: " << inLedgerRate
679 << "; TakerPays: " << aliceReducedOffer.in
680 << "; TakerGets: " << aliceReducedOffer.out
681 << std::endl;
682// #else
683 std::string_view filler = badRate ? "**" : " ";
684 std::cout << "| " << aliceReducedOffer.in << "` | `"
685 << aliceReducedOffer.out << "` | `" << initialRate
686 << "` | " << filler << "`" << inLedgerRate << "`"
687 << filler << std::endl;
688#endif
689 }
690
691 // In preparation for the next iteration make sure all three
692 // offers are gone from the ledger.
694 env,
695 {{alice, aliceOfferSeq},
696 {bob, bobOfferSeq},
697 {carol, carolOfferSeq}});
698 return badRate;
699 };
700
701 constexpr int loopCount = 100;
702 unsigned int blockedCount = 0;
703 {
704 STAmount increaseGets = USD(0);
705 STAmount const step(increaseGets.issue(), 1, -8);
706 for (unsigned int i = 0; i < loopCount; ++i)
707 {
708 blockedCount += exerciseOfferTrio(
709 Amounts(drops(1642020), USD(1) + increaseGets));
710 increaseGets += step;
711 }
712 }
713
714 // If fixReducedOffersV2 is enabled, then none of the test cases
715 // should produce a potentially blocking rate.
716 //
717 // Also verify that if fixReducedOffersV2 is not enabled then
718 // some of the test cases produced a potentially blocking rate.
719 if (features[fixReducedOffersV2])
720 {
721 BEAST_EXPECT(blockedCount == 0);
722 }
723 else
724 {
725 BEAST_EXPECT(blockedCount > 80);
726 }
727 }
728 }
729
730 void
739};
740
741BEAST_DEFINE_TESTSUITE_PRIO(ReducedOffer, app, ripple, 2);
742
743} // namespace test
744} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
int signum() const noexcept
Definition STAmount.h:514
Issue const & issue() const
Definition STAmount.h:496
void run() override
Runs the suite.
static void cleanupOldOffers(jtx::Env &env, std::initializer_list< std::pair< jtx::Account const &, std::uint32_t > > list)
static bool offerInLedger(jtx::Env &env, jtx::Account const &acct, std::uint32_t offerSeq)
static auto ledgerEntryOffer(jtx::Env &env, jtx::Account const &acct, std::uint32_t offer_seq)
Amounts jsonOfferToAmounts(Json::Value const &json)
Immutable cryptographic account descriptor.
Definition Account.h:39
std::string const & human() const
Returns the human readable public key.
Definition Account.h:118
A transaction testing environment.
Definition Env.h:121
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:122
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:791
Converts to IOU Issue or STAmount.
A balance matches.
Definition balance.h:39
balance(Account const &account, none_t)
Definition balance.h:46
Set the fee on a JTx.
Definition fee.h:37
Inject raw JSON.
Definition jtx_json.h:33
Set the flags on a JTx.
Definition txflags.h:31
T endl(T... args)
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition owners.h:92
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:32
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition rate.cpp:32
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:46
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
STAmount amountFromJson(SField const &name, Json::Value const &v)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
constexpr std::uint32_t tfSell
Definition TxFlags.h:101