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