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 // Make one test run without fixReducedOffersV1 and one with.
84 for (FeatureBitset features :
85 {testable_amendments() - fixReducedOffersV1,
86 testable_amendments() | fixReducedOffersV1})
87 {
88 Env env{*this, features};
89
90 // Make sure none of the offers we generate are under funded.
91 env.fund(XRP(10'000'000), gw, alice, bob);
92 env.close();
93
94 env(trust(alice, USD(10'000'000)));
95 env(trust(bob, USD(10'000'000)));
96 env.close();
97
98 env(pay(gw, bob, USD(10'000'000)));
99 env.close();
100
101 // Lambda that:
102 // 1. Exercises one offer pair,
103 // 2. Collects the results, and
104 // 3. Cleans up for the next offer pair.
105 // Returns 1 if the crossed offer has a bad rate for the book.
106 auto exerciseOfferPair =
107 [this, &env, &alice, &bob](
108 Amounts const& inLedger,
109 Amounts const& newOffer) -> unsigned int {
110 // Put inLedger offer in the ledger so newOffer can cross it.
111 std::uint32_t const aliceOfferSeq = env.seq(alice);
112 env(offer(alice, inLedger.in, inLedger.out));
113 env.close();
114
115 // Now alice's offer will partially cross bob's offer.
116 STAmount const initialRate = Quality(newOffer).rate();
117 std::uint32_t const bobOfferSeq = env.seq(bob);
118 STAmount const bobInitialBalance = env.balance(bob);
119 STAmount const bobsFee = env.current()->fees().base;
120 env(offer(bob, newOffer.in, newOffer.out, tfSell),
121 fee(bobsFee));
122 env.close();
123 STAmount const bobFinalBalance = env.balance(bob);
124
125 // alice's offer should be fully crossed and so gone from
126 // the ledger.
127 if (!BEAST_EXPECT(!offerInLedger(env, alice, aliceOfferSeq)))
128 // If the in-ledger offer was not consumed then further
129 // results are meaningless.
130 return 1;
131
132 // bob's offer should be in the ledger, but reduced in size.
133 unsigned int badRate = 1;
134 {
135 Json::Value bobOffer =
136 ledgerEntryOffer(env, bob, bobOfferSeq);
137
138 STAmount const reducedTakerGets = amountFromJson(
139 sfTakerGets, bobOffer[jss::node][sfTakerGets.jsonName]);
140 STAmount const reducedTakerPays = amountFromJson(
141 sfTakerPays, bobOffer[jss::node][sfTakerPays.jsonName]);
142 STAmount const bobGot =
143 env.balance(bob) + bobsFee - bobInitialBalance;
144 BEAST_EXPECT(reducedTakerPays < newOffer.in);
145 BEAST_EXPECT(reducedTakerGets < newOffer.out);
146 STAmount const inLedgerRate =
147 Quality(Amounts{reducedTakerPays, reducedTakerGets})
148 .rate();
149
150 badRate = inLedgerRate > initialRate ? 1 : 0;
151
152 // If the inLedgerRate is less than initial rate, then
153 // incrementing the mantissa of the reduced taker pays
154 // should result in a rate higher than initial. Check
155 // this to verify that the largest allowable TakerPays
156 // was computed.
157 if (badRate == 0)
158 {
159 STAmount const tweakedTakerPays =
160 reducedTakerPays + drops(1);
161 STAmount const tweakedRate =
162 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
163 .rate();
164 BEAST_EXPECT(tweakedRate > initialRate);
165 }
166#if 0
167 std::cout << "Placed rate: " << initialRate
168 << "; in-ledger rate: " << inLedgerRate
169 << "; TakerPays: " << reducedTakerPays
170 << "; TakerGets: " << reducedTakerGets
171 << "; bob already got: " << bobGot << std::endl;
172// #else
173 std::string_view filler =
174 inLedgerRate > initialRate ? "**" : " ";
175 std::cout << "| `" << reducedTakerGets << "` | `"
176 << reducedTakerPays << "` | `" << initialRate
177 << "` | " << filler << "`" << inLedgerRate << "`"
178 << filler << " |`" << std::endl;
179#endif
180 }
181
182 // In preparation for the next iteration make sure the two
183 // offers are gone from the ledger.
185 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
186 return badRate;
187 };
188
189 // bob's offer (the new offer) is the same every time:
190 Amounts const bobsOffer{
191 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
192
193 // alice's offer has a slightly smaller TakerPays with each
194 // iteration. This should mean that the size of the offer bob
195 // places in the ledger should increase with each iteration.
196 unsigned int blockedCount = 0;
197 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
198 mantissaReduce <= 5'000'000'000ull;
199 mantissaReduce += 20'000'000ull)
200 {
201 STAmount aliceUSD{
202 bobsOffer.out.issue(),
203 bobsOffer.out.mantissa() - mantissaReduce,
204 bobsOffer.out.exponent()};
205 STAmount aliceXRP{
206 bobsOffer.in.issue(), bobsOffer.in.mantissa() - 1};
207 Amounts alicesOffer{aliceUSD, aliceXRP};
208 blockedCount += exerciseOfferPair(alicesOffer, bobsOffer);
209 }
210
211 // If fixReducedOffersV1 is enabled, then none of the test cases
212 // should produce a potentially blocking rate.
213 //
214 // Also verify that if fixReducedOffersV1 is not enabled then
215 // some of the test cases produced a potentially blocking rate.
216 if (features[fixReducedOffersV1])
217 {
218 BEAST_EXPECT(blockedCount == 0);
219 }
220 else
221 {
222 BEAST_EXPECT(blockedCount >= 170);
223 }
224 }
225 }
226
227 void
229 {
230 testcase("exercise partial cross old XRP/IOU offer Q change");
231
232 using namespace jtx;
233
234 auto const gw = Account{"gateway"};
235 auto const alice = Account{"alice"};
236 auto const bob = Account{"bob"};
237 auto const USD = gw["USD"];
238
239 // Make one test run without fixReducedOffersV1 and one with.
240 for (FeatureBitset features :
241 {testable_amendments() - fixReducedOffersV1,
242 testable_amendments() | fixReducedOffersV1})
243 {
244 // Make sure none of the offers we generate are under funded.
245 Env env{*this, features};
246 env.fund(XRP(10'000'000), gw, alice, bob);
247 env.close();
248
249 env(trust(alice, USD(10'000'000)));
250 env(trust(bob, USD(10'000'000)));
251 env.close();
252
253 env(pay(gw, alice, USD(10'000'000)));
254 env.close();
255
256 // Lambda that:
257 // 1. Exercises one offer pair,
258 // 2. Collects the results, and
259 // 3. Cleans up for the next offer pair.
260 auto exerciseOfferPair =
261 [this, &env, &alice, &bob](
262 Amounts const& inLedger,
263 Amounts const& newOffer) -> unsigned int {
264 // Get the inLedger offer into the ledger so newOffer can cross
265 // it.
266 STAmount const initialRate = Quality(inLedger).rate();
267 std::uint32_t const aliceOfferSeq = env.seq(alice);
268 env(offer(alice, inLedger.in, inLedger.out));
269 env.close();
270
271 // Now bob's offer will partially cross alice's offer.
272 std::uint32_t const bobOfferSeq = env.seq(bob);
273 STAmount const aliceInitialBalance = env.balance(alice);
274 env(offer(bob, newOffer.in, newOffer.out));
275 env.close();
276 STAmount const aliceFinalBalance = env.balance(alice);
277
278 // bob's offer should not have made it into the ledger.
279 if (!BEAST_EXPECT(!offerInLedger(env, bob, bobOfferSeq)))
280 {
281 // If the in-ledger offer was not consumed then further
282 // results are meaningless.
284 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
285 return 1;
286 }
287 // alice's offer should still be in the ledger, but reduced in
288 // size.
289 unsigned int badRate = 1;
290 {
291 Json::Value aliceOffer =
292 ledgerEntryOffer(env, alice, aliceOfferSeq);
293
294 STAmount const reducedTakerGets = amountFromJson(
295 sfTakerGets,
296 aliceOffer[jss::node][sfTakerGets.jsonName]);
297 STAmount const reducedTakerPays = amountFromJson(
298 sfTakerPays,
299 aliceOffer[jss::node][sfTakerPays.jsonName]);
300 STAmount const aliceGot =
301 env.balance(alice) - aliceInitialBalance;
302 BEAST_EXPECT(reducedTakerPays < inLedger.in);
303 BEAST_EXPECT(reducedTakerGets < inLedger.out);
304 STAmount const inLedgerRate =
305 Quality(Amounts{reducedTakerPays, reducedTakerGets})
306 .rate();
307 badRate = inLedgerRate > initialRate ? 1 : 0;
308
309 // If the inLedgerRate is less than initial rate, then
310 // incrementing the mantissa of the reduced taker pays
311 // should result in a rate higher than initial. Check
312 // this to verify that the largest allowable TakerPays
313 // was computed.
314 if (badRate == 0)
315 {
316 STAmount const tweakedTakerPays =
317 reducedTakerPays + drops(1);
318 STAmount const tweakedRate =
319 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
320 .rate();
321 BEAST_EXPECT(tweakedRate > initialRate);
322 }
323#if 0
324 std::cout << "Placed rate: " << initialRate
325 << "; in-ledger rate: " << inLedgerRate
326 << "; TakerPays: " << reducedTakerPays
327 << "; TakerGets: " << reducedTakerGets
328 << "; alice already got: " << aliceGot
329 << std::endl;
330// #else
331 std::string_view filler = badRate ? "**" : " ";
332 std::cout << "| `" << reducedTakerGets << "` | `"
333 << reducedTakerPays << "` | `" << initialRate
334 << "` | " << filler << "`" << inLedgerRate << "`"
335 << filler << " | `" << aliceGot << "` |"
336 << std::endl;
337#endif
338 }
339
340 // In preparation for the next iteration make sure the two
341 // offers are gone from the ledger.
343 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
344 return badRate;
345 };
346
347 // alice's offer (the old offer) is the same every time:
348 Amounts const aliceOffer{
349 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
350
351 // bob's offer has a slightly smaller TakerPays with each iteration.
352 // This should mean that the size of the offer alice leaves in the
353 // ledger should increase with each iteration.
354 unsigned int blockedCount = 0;
355 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
356 mantissaReduce <= 4'000'000'000ull;
357 mantissaReduce += 20'000'000ull)
358 {
359 STAmount bobUSD{
360 aliceOffer.out.issue(),
361 aliceOffer.out.mantissa() - mantissaReduce,
362 aliceOffer.out.exponent()};
363 STAmount bobXRP{
364 aliceOffer.in.issue(), aliceOffer.in.mantissa() - 1};
365 Amounts bobsOffer{bobUSD, bobXRP};
366
367 blockedCount += exerciseOfferPair(aliceOffer, bobsOffer);
368 }
369
370 // If fixReducedOffersV1 is enabled, then none of the test cases
371 // should produce a potentially blocking rate.
372 //
373 // Also verify that if fixReducedOffersV1 is not enabled then
374 // some of the test cases produced a potentially blocking rate.
375 if (features[fixReducedOffersV1])
376 {
377 BEAST_EXPECT(blockedCount == 0);
378 }
379 else
380 {
381 BEAST_EXPECT(blockedCount > 10);
382 }
383 }
384 }
385
386 void
388 {
389 testcase("exercise underfunded XRP/IOU offer Q change");
390
391 // Bob places an offer that is not fully funded.
392 //
393 // This unit test compares the behavior of this situation before and
394 // after applying the fixReducedOffersV1 amendment.
395
396 using namespace jtx;
397 auto const alice = Account{"alice"};
398 auto const bob = Account{"bob"};
399 auto const gw = Account{"gw"};
400 auto const USD = gw["USD"];
401
402 // Make one test run without fixReducedOffersV1 and one with.
403 for (FeatureBitset features :
404 {testable_amendments() - fixReducedOffersV1,
405 testable_amendments() | fixReducedOffersV1})
406 {
407 Env env{*this, features};
408
409 env.fund(XRP(10000), alice, bob, gw);
410 env.close();
411 env.trust(USD(1000), alice, bob);
412
413 int blockedOrderBookCount = 0;
414 for (STAmount initialBobUSD = USD(0.45); initialBobUSD <= USD(1);
415 initialBobUSD += USD(0.025))
416 {
417 // underfund bob's offer
418 env(pay(gw, bob, initialBobUSD));
419 env.close();
420
421 std::uint32_t const bobOfferSeq = env.seq(bob);
422 env(offer(bob, drops(2), USD(1)));
423 env.close();
424
425 // alice places an offer that would cross bob's if bob's were
426 // well funded.
427 std::uint32_t const aliceOfferSeq = env.seq(alice);
428 env(offer(alice, USD(1), drops(2)));
429 env.close();
430
431 // We want to detect order book blocking. If:
432 // 1. bob's offer is still in the ledger and
433 // 2. alice received no USD
434 // then we use that as evidence that bob's offer blocked the
435 // order book.
436 {
437 bool const bobsOfferGone =
438 !offerInLedger(env, bob, bobOfferSeq);
439 STAmount const aliceBalanceUSD = env.balance(alice, USD);
440
441 // Sanity check the ledger if alice got USD.
442 if (aliceBalanceUSD.signum() > 0)
443 {
444 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
445 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
446 BEAST_EXPECT(bobsOfferGone);
447 }
448
449 // Track occurrences of order book blocking.
450 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
451 {
452 ++blockedOrderBookCount;
453 }
454
455 // In preparation for the next iteration clean up any
456 // leftover offers.
458 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
459
460 // Zero out alice's and bob's USD balances.
461 if (STAmount const aliceBalance = env.balance(alice, USD);
462 aliceBalance.signum() > 0)
463 env(pay(alice, gw, aliceBalance));
464
465 if (STAmount const bobBalance = env.balance(bob, USD);
466 bobBalance.signum() > 0)
467 env(pay(bob, gw, bobBalance));
468
469 env.close();
470 }
471 }
472
473 // If fixReducedOffersV1 is enabled, then none of the test cases
474 // should produce a potentially blocking rate.
475 //
476 // Also verify that if fixReducedOffersV1 is not enabled then
477 // some of the test cases produced a potentially blocking rate.
478 if (features[fixReducedOffersV1])
479 {
480 BEAST_EXPECT(blockedOrderBookCount == 0);
481 }
482 else
483 {
484 BEAST_EXPECT(blockedOrderBookCount > 15);
485 }
486 }
487 }
488
489 void
491 {
492 testcase("exercise underfunded IOU/IOU offer Q change");
493
494 // Bob places an IOU/IOU offer that is not fully funded.
495 //
496 // This unit test compares the behavior of this situation before and
497 // after applying the fixReducedOffersV1 amendment.
498
499 using namespace jtx;
500 using namespace std::chrono_literals;
501 auto const alice = Account{"alice"};
502 auto const bob = Account{"bob"};
503 auto const gw = Account{"gw"};
504
505 auto const USD = gw["USD"];
506 auto const EUR = gw["EUR"];
507
508 STAmount const tinyUSD(USD.issue(), /*mantissa*/ 1, /*exponent*/ -81);
509
510 // Make one test run without fixReducedOffersV1 and one with.
511 for (FeatureBitset features :
512 {testable_amendments() - fixReducedOffersV1,
513 testable_amendments() | fixReducedOffersV1})
514 {
515 Env env{*this, features};
516
517 env.fund(XRP(10000), alice, bob, gw);
518 env.close();
519 env.trust(USD(1000), alice, bob);
520 env.trust(EUR(1000), alice, bob);
521
522 STAmount const eurOffer(
523 EUR.issue(), /*mantissa*/ 2957, /*exponent*/ -76);
524 STAmount const usdOffer(
525 USD.issue(), /*mantissa*/ 7109, /*exponent*/ -76);
526
527 STAmount const endLoop(
528 USD.issue(), /*mantissa*/ 50, /*exponent*/ -81);
529
530 int blockedOrderBookCount = 0;
531 for (STAmount initialBobUSD = tinyUSD; initialBobUSD <= endLoop;
532 initialBobUSD += tinyUSD)
533 {
534 // underfund bob's offer
535 env(pay(gw, bob, initialBobUSD));
536 env(pay(gw, alice, EUR(100)));
537 env.close();
538
539 // This offer is underfunded
540 std::uint32_t bobOfferSeq = env.seq(bob);
541 env(offer(bob, eurOffer, usdOffer));
542 env.close();
543 env.require(offers(bob, 1));
544
545 // alice places an offer that crosses bob's.
546 std::uint32_t aliceOfferSeq = env.seq(alice);
547 env(offer(alice, usdOffer, eurOffer));
548 env.close();
549
550 // Examine the aftermath of alice's offer.
551 {
552 bool const bobsOfferGone =
553 !offerInLedger(env, bob, bobOfferSeq);
554 STAmount aliceBalanceUSD = env.balance(alice, USD);
555#if 0
557 << "bobs initial: " << initialBobUSD
558 << "; alice final: " << aliceBalanceUSD
559 << "; bobs offer: " << bobsOfferJson.toStyledString()
560 << std::endl;
561#endif
562 // Sanity check the ledger if alice got USD.
563 if (aliceBalanceUSD.signum() > 0)
564 {
565 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
566 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
567 BEAST_EXPECT(bobsOfferGone);
568 }
569
570 // Track occurrences of order book blocking.
571 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
572 {
573 ++blockedOrderBookCount;
574 }
575 }
576
577 // In preparation for the next iteration clean up any
578 // leftover offers.
580 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
581
582 // Zero out alice's and bob's IOU balances.
583 auto zeroBalance = [&env, &gw](
584 Account const& acct, IOU const& iou) {
585 if (STAmount const balance = env.balance(acct, iou);
586 balance.signum() > 0)
587 env(pay(acct, gw, balance));
588 };
589
590 zeroBalance(alice, EUR);
591 zeroBalance(alice, USD);
592 zeroBalance(bob, EUR);
593 zeroBalance(bob, USD);
594 env.close();
595 }
596
597 // If fixReducedOffersV1 is enabled, then none of the test cases
598 // should produce a potentially blocking rate.
599 //
600 // Also verify that if fixReducedOffersV1 is not enabled then
601 // some of the test cases produced a potentially blocking rate.
602 if (features[fixReducedOffersV1])
603 {
604 BEAST_EXPECT(blockedOrderBookCount == 0);
605 }
606 else
607 {
608 BEAST_EXPECT(blockedOrderBookCount > 20);
609 }
610 }
611 }
612
613 Amounts
615 {
616 STAmount const in =
617 amountFromJson(sfTakerPays, json[sfTakerPays.jsonName]);
618 STAmount const out =
619 amountFromJson(sfTakerGets, json[sfTakerGets.jsonName]);
620 return {in, out};
621 }
622
623 void
625 {
626 // This test case was motivated by Issue #4937. It recreates
627 // the specific failure identified in that issue and samples some other
628 // cases in the same vicinity to make sure that the new behavior makes
629 // sense.
630 testcase("exercise tfSell partial cross old XRP/IOU offer Q change");
631
632 using namespace jtx;
633
634 Account const gw("gateway");
635 Account const alice("alice");
636 Account const bob("bob");
637 Account const carol("carol");
638 auto const USD = gw["USD"];
639
640 // Make one test run without fixReducedOffersV2 and one with.
641 for (FeatureBitset features :
642 {testable_amendments() - fixReducedOffersV2,
643 testable_amendments() | fixReducedOffersV2})
644 {
645 // Make sure none of the offers we generate are under funded.
646 Env env{*this, features};
647 env.fund(XRP(10'000'000), gw, alice, bob, carol);
648 env.close();
649
650 env(trust(alice, USD(10'000'000)));
651 env(trust(bob, USD(10'000'000)));
652 env(trust(carol, USD(10'000'000)));
653 env.close();
654
655 env(pay(gw, alice, USD(10'000'000)));
656 env(pay(gw, bob, USD(10'000'000)));
657 env(pay(gw, carol, USD(10'000'000)));
658 env.close();
659
660 // Lambda that:
661 // 1. Exercises one offer trio,
662 // 2. Collects the results, and
663 // 3. Cleans up for the next offer trio.
664 auto exerciseOfferTrio =
665 [this, &env, &alice, &bob, &carol, &USD](
666 Amounts const& carolOffer) -> unsigned int {
667 // alice submits an offer that may become a blocker.
668 std::uint32_t const aliceOfferSeq = env.seq(alice);
669 static Amounts const aliceInitialOffer(USD(2), drops(3382562));
670 env(offer(alice, aliceInitialOffer.in, aliceInitialOffer.out));
671 env.close();
672 STAmount const initialRate =
674 env, alice, aliceOfferSeq)[jss::node]))
675 .rate();
676
677 // bob submits an offer that is more desirable than alice's
678 std::uint32_t const bobOfferSeq = env.seq(bob);
679 env(offer(bob, USD(0.97086565812384), drops(1642020)));
680 env.close();
681
682 // Now carol's offer consumes bob's and partially crosses
683 // alice's. The tfSell flag is important.
684 std::uint32_t const carolOfferSeq = env.seq(carol);
685 env(offer(carol, carolOffer.in, carolOffer.out),
686 txflags(tfSell));
687 env.close();
688
689 // carol's offer should not have made it into the ledger and
690 // bob's offer should be fully consumed.
691 if (!BEAST_EXPECT(
692 !offerInLedger(env, carol, carolOfferSeq) &&
693 !offerInLedger(env, bob, bobOfferSeq)))
694 {
695 // If carol's or bob's offers are still in the ledger then
696 // further results are meaningless.
698 env,
699 {{alice, aliceOfferSeq},
700 {bob, bobOfferSeq},
701 {carol, carolOfferSeq}});
702 return 1;
703 }
704 // alice's offer should still be in the ledger, but reduced in
705 // size.
706 unsigned int badRate = 1;
707 {
708 Json::Value aliceOffer =
709 ledgerEntryOffer(env, alice, aliceOfferSeq);
710
711 Amounts aliceReducedOffer =
712 jsonOfferToAmounts(aliceOffer[jss::node]);
713
714 BEAST_EXPECT(aliceReducedOffer.in < aliceInitialOffer.in);
715 BEAST_EXPECT(aliceReducedOffer.out < aliceInitialOffer.out);
716 STAmount const inLedgerRate =
717 Quality(aliceReducedOffer).rate();
718 badRate = inLedgerRate > initialRate ? 1 : 0;
719
720 // If the inLedgerRate is less than initial rate, then
721 // incrementing the mantissa of the reduced TakerGets
722 // should result in a rate higher than initial. Check
723 // this to verify that the largest allowable TakerGets
724 // was computed.
725 if (badRate == 0)
726 {
727 STAmount const tweakedTakerGets(
728 aliceReducedOffer.in.issue(),
729 aliceReducedOffer.in.mantissa() + 1,
730 aliceReducedOffer.in.exponent(),
731 aliceReducedOffer.in.negative());
732 STAmount const tweakedRate =
733 Quality(
734 Amounts{aliceReducedOffer.in, tweakedTakerGets})
735 .rate();
736 BEAST_EXPECT(tweakedRate > initialRate);
737 }
738#if 0
739 std::cout << "Placed rate: " << initialRate
740 << "; in-ledger rate: " << inLedgerRate
741 << "; TakerPays: " << aliceReducedOffer.in
742 << "; TakerGets: " << aliceReducedOffer.out
743 << std::endl;
744// #else
745 std::string_view filler = badRate ? "**" : " ";
746 std::cout << "| " << aliceReducedOffer.in << "` | `"
747 << aliceReducedOffer.out << "` | `" << initialRate
748 << "` | " << filler << "`" << inLedgerRate << "`"
749 << filler << std::endl;
750#endif
751 }
752
753 // In preparation for the next iteration make sure all three
754 // offers are gone from the ledger.
756 env,
757 {{alice, aliceOfferSeq},
758 {bob, bobOfferSeq},
759 {carol, carolOfferSeq}});
760 return badRate;
761 };
762
763 constexpr int loopCount = 100;
764 unsigned int blockedCount = 0;
765 {
766 STAmount increaseGets = USD(0);
767 STAmount const step(increaseGets.issue(), 1, -8);
768 for (unsigned int i = 0; i < loopCount; ++i)
769 {
770 blockedCount += exerciseOfferTrio(
771 Amounts(drops(1642020), USD(1) + increaseGets));
772 increaseGets += step;
773 }
774 }
775
776 // If fixReducedOffersV2 is enabled, then none of the test cases
777 // should produce a potentially blocking rate.
778 //
779 // Also verify that if fixReducedOffersV2 is not enabled then
780 // some of the test cases produced a potentially blocking rate.
781 if (features[fixReducedOffersV2])
782 {
783 BEAST_EXPECT(blockedCount == 0);
784 }
785 else
786 {
787 BEAST_EXPECT(blockedCount > 80);
788 }
789 }
790 }
791
792 void
801};
802
803BEAST_DEFINE_TESTSUITE_PRIO(ReducedOffer, app, ripple, 2);
804
805} // namespace test
806} // 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:121
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