21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/Quality.h>
23#include <xrpl/protocol/jss.h>
39 jvParams[jss::offer][jss::account] = acct.
human();
40 jvParams[jss::offer][jss::seq] = offer_seq;
42 "json",
"ledger_entry",
to_string(jvParams))[jss::result];
54 ledgerOffer[jss::error].
asString() ==
"entryNotFound");
64 for (
auto [acct, offerSeq] : list)
73 testcase(
"exercise partial cross new XRP/IOU offer Q change");
77 auto const gw =
Account{
"gateway"};
78 auto const alice =
Account{
"alice"};
79 auto const bob =
Account{
"bob"};
80 auto const USD = gw[
"USD"];
87 Env env{*
this, features};
90 env.fund(
XRP(10'000'000), gw, alice, bob);
93 env(
trust(alice, USD(10'000'000)));
94 env(
trust(bob, USD(10'000'000)));
97 env(
pay(gw, bob, USD(10'000'000)));
105 auto exerciseOfferPair =
106 [
this, &env, &alice, &bob](
107 Amounts
const& inLedger,
108 Amounts
const& newOffer) ->
unsigned int {
111 env(
offer(alice, inLedger.in, inLedger.out));
115 STAmount const initialRate = Quality(newOffer).rate();
117 STAmount const bobInitialBalance = env.balance(bob);
122 STAmount const bobFinalBalance = env.balance(bob);
126 if (!BEAST_EXPECT(!
offerInLedger(env, alice, aliceOfferSeq)))
132 unsigned int badRate = 1;
138 sfTakerGets, bobOffer[jss::node][sfTakerGets.jsonName]);
140 sfTakerPays, bobOffer[jss::node][sfTakerPays.jsonName]);
142 env.balance(bob) + bobsFee - bobInitialBalance;
143 BEAST_EXPECT(reducedTakerPays < newOffer.in);
144 BEAST_EXPECT(reducedTakerGets < newOffer.out);
146 Quality(Amounts{reducedTakerPays, reducedTakerGets})
149 badRate = inLedgerRate > initialRate ? 1 : 0;
159 reducedTakerPays +
drops(1);
161 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
163 BEAST_EXPECT(tweakedRate > initialRate);
166 std::cout <<
"Placed rate: " << initialRate
167 <<
"; in-ledger rate: " << inLedgerRate
168 <<
"; TakerPays: " << reducedTakerPays
169 <<
"; TakerGets: " << reducedTakerGets
170 <<
"; bob already got: " << bobGot <<
std::endl;
173 inLedgerRate > initialRate ?
"**" :
" ";
174 std::cout <<
"| `" << reducedTakerGets <<
"` | `"
175 << reducedTakerPays <<
"` | `" << initialRate
176 <<
"` | " << filler <<
"`" << inLedgerRate <<
"`"
184 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
189 Amounts
const bobsOffer{
195 unsigned int blockedCount = 0;
197 mantissaReduce <= 5'000'000'000ull;
198 mantissaReduce += 20'000'000ull)
201 bobsOffer.out.
issue(),
202 bobsOffer.out.mantissa() - mantissaReduce,
203 bobsOffer.out.exponent()};
205 bobsOffer.in.
issue(), bobsOffer.in.mantissa() - 1};
206 Amounts alicesOffer{aliceUSD, aliceXRP};
207 blockedCount += exerciseOfferPair(alicesOffer, bobsOffer);
215 if (features[fixReducedOffersV1])
217 BEAST_EXPECT(blockedCount == 0);
221 BEAST_EXPECT(blockedCount >= 170);
229 testcase(
"exercise partial cross old XRP/IOU offer Q change");
233 auto const gw =
Account{
"gateway"};
234 auto const alice =
Account{
"alice"};
235 auto const bob =
Account{
"bob"};
236 auto const USD = gw[
"USD"];
244 Env env{*
this, features};
245 env.fund(
XRP(10'000'000), gw, alice, bob);
248 env(
trust(alice, USD(10'000'000)));
249 env(
trust(bob, USD(10'000'000)));
252 env(
pay(gw, alice, USD(10'000'000)));
259 auto exerciseOfferPair =
260 [
this, &env, &alice, &bob](
261 Amounts
const& inLedger,
262 Amounts
const& newOffer) ->
unsigned int {
265 STAmount const initialRate = Quality(inLedger).rate();
267 env(
offer(alice, inLedger.in, inLedger.out));
272 STAmount const aliceInitialBalance = env.balance(alice);
273 env(
offer(bob, newOffer.in, newOffer.out));
275 STAmount const aliceFinalBalance = env.balance(alice);
283 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
288 unsigned int badRate = 1;
295 aliceOffer[jss::node][sfTakerGets.jsonName]);
298 aliceOffer[jss::node][sfTakerPays.jsonName]);
300 env.balance(alice) - aliceInitialBalance;
301 BEAST_EXPECT(reducedTakerPays < inLedger.in);
302 BEAST_EXPECT(reducedTakerGets < inLedger.out);
304 Quality(Amounts{reducedTakerPays, reducedTakerGets})
306 badRate = inLedgerRate > initialRate ? 1 : 0;
316 reducedTakerPays +
drops(1);
318 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
320 BEAST_EXPECT(tweakedRate > initialRate);
323 std::cout <<
"Placed rate: " << initialRate
324 <<
"; in-ledger rate: " << inLedgerRate
325 <<
"; TakerPays: " << reducedTakerPays
326 <<
"; TakerGets: " << reducedTakerGets
327 <<
"; alice already got: " << aliceGot
331 std::cout <<
"| `" << reducedTakerGets <<
"` | `"
332 << reducedTakerPays <<
"` | `" << initialRate
333 <<
"` | " << filler <<
"`" << inLedgerRate <<
"`"
334 << filler <<
" | `" << aliceGot <<
"` |"
342 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
347 Amounts
const aliceOffer{
353 unsigned int blockedCount = 0;
355 mantissaReduce <= 4'000'000'000ull;
356 mantissaReduce += 20'000'000ull)
359 aliceOffer.out.
issue(),
360 aliceOffer.out.mantissa() - mantissaReduce,
361 aliceOffer.out.exponent()};
363 aliceOffer.in.
issue(), aliceOffer.in.mantissa() - 1};
364 Amounts bobsOffer{bobUSD, bobXRP};
366 blockedCount += exerciseOfferPair(aliceOffer, bobsOffer);
374 if (features[fixReducedOffersV1])
376 BEAST_EXPECT(blockedCount == 0);
380 BEAST_EXPECT(blockedCount > 10);
388 testcase(
"exercise underfunded XRP/IOU offer Q change");
396 auto const alice =
Account{
"alice"};
397 auto const bob =
Account{
"bob"};
399 auto const USD = gw[
"USD"];
406 Env env{*
this, features};
408 env.fund(
XRP(10000), alice, bob, gw);
410 env.trust(USD(1000), alice, bob);
412 int blockedOrderBookCount = 0;
413 for (
STAmount initialBobUSD = USD(0.45); initialBobUSD <= USD(1);
414 initialBobUSD += USD(0.025))
417 env(
pay(gw, bob, initialBobUSD));
436 bool const bobsOfferGone =
438 STAmount const aliceBalanceUSD = env.balance(alice, USD);
441 if (aliceBalanceUSD.
signum() > 0)
443 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
444 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
445 BEAST_EXPECT(bobsOfferGone);
449 if (!bobsOfferGone && aliceBalanceUSD.
signum() == 0)
451 ++blockedOrderBookCount;
457 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
460 if (
STAmount const aliceBalance = env.balance(alice, USD);
461 aliceBalance.
signum() > 0)
462 env(
pay(alice, gw, aliceBalance));
464 if (
STAmount const bobBalance = env.balance(bob, USD);
466 env(
pay(bob, gw, bobBalance));
477 if (features[fixReducedOffersV1])
479 BEAST_EXPECT(blockedOrderBookCount == 0);
483 BEAST_EXPECT(blockedOrderBookCount > 15);
491 testcase(
"exercise underfunded IOU/IOU offer Q change");
499 using namespace std::chrono_literals;
500 auto const alice =
Account{
"alice"};
501 auto const bob =
Account{
"bob"};
504 auto const USD = gw[
"USD"];
505 auto const EUR = gw[
"EUR"];
507 STAmount const tinyUSD(USD.issue(), 1, -81);
514 Env env{*
this, features};
516 env.fund(
XRP(10000), alice, bob, gw);
518 env.trust(USD(1000), alice, bob);
519 env.trust(EUR(1000), alice, bob);
522 EUR.issue(), 2957, -76);
524 USD.issue(), 7109, -76);
527 USD.issue(), 50, -81);
529 int blockedOrderBookCount = 0;
530 for (
STAmount initialBobUSD = tinyUSD; initialBobUSD <= endLoop;
531 initialBobUSD += tinyUSD)
534 env(
pay(gw, bob, initialBobUSD));
535 env(
pay(gw, alice, EUR(100)));
540 env(
offer(bob, eurOffer, usdOffer));
542 env.require(
offers(bob, 1));
546 env(
offer(alice, usdOffer, eurOffer));
551 bool const bobsOfferGone =
553 STAmount aliceBalanceUSD = env.balance(alice, USD);
556 <<
"bobs initial: " << initialBobUSD
557 <<
"; alice final: " << aliceBalanceUSD
558 <<
"; bobs offer: " << bobsOfferJson.toStyledString()
562 if (aliceBalanceUSD.
signum() > 0)
564 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
565 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
566 BEAST_EXPECT(bobsOfferGone);
570 if (!bobsOfferGone && aliceBalanceUSD.
signum() == 0)
572 ++blockedOrderBookCount;
579 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
582 auto zeroBalance = [&env, &gw](
589 zeroBalance(alice, EUR);
590 zeroBalance(alice, USD);
591 zeroBalance(bob, EUR);
592 zeroBalance(bob, USD);
601 if (features[fixReducedOffersV1])
603 BEAST_EXPECT(blockedOrderBookCount == 0);
607 BEAST_EXPECT(blockedOrderBookCount > 20);
629 testcase(
"exercise tfSell partial cross old XRP/IOU offer Q change");
637 auto const USD = gw[
"USD"];
645 Env env{*
this, features};
646 env.fund(
XRP(10'000'000), gw, alice, bob, carol);
649 env(
trust(alice, USD(10'000'000)));
650 env(
trust(bob, USD(10'000'000)));
651 env(
trust(carol, USD(10'000'000)));
654 env(
pay(gw, alice, USD(10'000'000)));
655 env(
pay(gw, bob, USD(10'000'000)));
656 env(
pay(gw, carol, USD(10'000'000)));
663 auto exerciseOfferTrio =
664 [
this, &env, &alice, &bob, &carol, &USD](
665 Amounts
const& carolOffer) ->
unsigned int {
668 static Amounts
const aliceInitialOffer(USD(2),
drops(3382562));
669 env(
offer(alice, aliceInitialOffer.in, aliceInitialOffer.out));
673 env, alice, aliceOfferSeq)[jss::node]))
678 env(
offer(bob, USD(0.97086565812384),
drops(1642020)));
684 env(
offer(carol, carolOffer.in, carolOffer.out),
698 {{alice, aliceOfferSeq},
700 {carol, carolOfferSeq}});
705 unsigned int badRate = 1;
710 Amounts aliceReducedOffer =
713 BEAST_EXPECT(aliceReducedOffer.in < aliceInitialOffer.in);
714 BEAST_EXPECT(aliceReducedOffer.out < aliceInitialOffer.out);
716 Quality(aliceReducedOffer).rate();
717 badRate = inLedgerRate > initialRate ? 1 : 0;
727 aliceReducedOffer.in.issue(),
728 aliceReducedOffer.in.mantissa() + 1,
729 aliceReducedOffer.in.exponent(),
730 aliceReducedOffer.in.negative());
733 Amounts{aliceReducedOffer.in, tweakedTakerGets})
735 BEAST_EXPECT(tweakedRate > initialRate);
738 std::cout <<
"Placed rate: " << initialRate
739 <<
"; in-ledger rate: " << inLedgerRate
740 <<
"; TakerPays: " << aliceReducedOffer.in
741 <<
"; TakerGets: " << aliceReducedOffer.out
745 std::cout <<
"| " << aliceReducedOffer.in <<
"` | `"
746 << aliceReducedOffer.out <<
"` | `" << initialRate
747 <<
"` | " << filler <<
"`" << inLedgerRate <<
"`"
756 {{alice, aliceOfferSeq},
758 {carol, carolOfferSeq}});
762 constexpr int loopCount = 100;
763 unsigned int blockedCount = 0;
767 for (
unsigned int i = 0; i < loopCount; ++i)
769 blockedCount += exerciseOfferTrio(
770 Amounts(
drops(1642020), USD(1) + increaseGets));
771 increaseGets += step;
780 if (features[fixReducedOffersV2])
782 BEAST_EXPECT(blockedCount == 0);
786 BEAST_EXPECT(blockedCount > 80);
802BEAST_DEFINE_TESTSUITE_PRIO(ReducedOffer, tx,
ripple, 2);
std::string asString() const
Returns the unquoted string value.
bool isMember(const char *key) const
Return true if the object has a member named key.
testcase_t testcase
Memberspace for declaring test cases.
int signum() const noexcept
Issue const & issue() const
void testPartialCrossOldXrpIouQChange()
void testUnderFundedXrpIouQChange()
void run() override
Runs the suite.
void testSellPartialCrossOldXrpIouQChange()
void testUnderFundedIouIouQChange()
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)
void testPartialCrossNewXrpIouQChange()
Immutable cryptographic account descriptor.
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Converts to IOU Issue or STAmount.
balance(Account const &account, none_t)
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
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.
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
XRP_t const XRP
Converts to XRP Issue or STAmount.
FeatureBitset supported_amendments()
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
STAmount amountFromJson(SField const &name, Json::Value const &v)
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr std::uint32_t tfSell