2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/balance.h>
6#include <test/jtx/fee.h>
7#include <test/jtx/offer.h>
8#include <test/jtx/owners.h>
9#include <test/jtx/pay.h>
10#include <test/jtx/trust.h>
11#include <test/jtx/txflags.h>
13#include <xrpl/beast/unit_test/suite.h>
14#include <xrpl/json/json_value.h>
15#include <xrpl/json/to_string.h>
16#include <xrpl/protocol/Feature.h>
17#include <xrpl/protocol/Quality.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/TxFlags.h>
21#include <xrpl/protocol/jss.h>
35 jvParams[jss::offer][jss::account] = acct.
human();
36 jvParams[jss::offer][jss::seq] = offerSeq;
37 return env.
rpc(
"json",
"ledger_entry",
to_string(jvParams))[jss::result];
46 ledgerOffer[jss::error].asString() ==
"entryNotFound");
55 for (
auto [acct, offerSeq] : list)
64 testcase(
"exercise partial cross new XRP/IOU offer Q change");
68 auto const gw =
Account{
"gateway"};
69 auto const alice =
Account{
"alice"};
70 auto const bob =
Account{
"bob"};
71 auto const usd = gw[
"USD"];
77 env.
fund(
XRP(10'000'000), gw, alice, bob);
80 env(
trust(alice, usd(10'000'000)));
81 env(
trust(bob, usd(10'000'000)));
84 env(
pay(gw, bob, usd(10'000'000)));
92 auto exerciseOfferPair = [
this, &env, &alice, &bob](
94 Amounts const& newOffer) ->
unsigned int {
97 env(
offer(alice, inLedger.in, inLedger.out));
105 env(
offer(bob, newOffer.in, newOffer.out, tfSell),
Fee(bobFee));
111 if (!BEAST_EXPECT(!
offerInLedger(env, alice, aliceOfferSeq)))
119 unsigned int badRate = 1;
124 amountFromJson(sfTakerGets, bobOffer[jss::node][sfTakerGets.jsonName]);
126 amountFromJson(sfTakerPays, bobOffer[jss::node][sfTakerPays.jsonName]);
128 BEAST_EXPECT(reducedTakerPays < newOffer.in);
129 BEAST_EXPECT(reducedTakerGets < newOffer.out);
133 badRate = inLedgerRate > initialRate ? 1 : 0;
142 STAmount const tweakedTakerPays = reducedTakerPays +
drops(1);
145 BEAST_EXPECT(tweakedRate > initialRate);
148 std::cout <<
"Placed rate: " << initialRate
149 <<
"; in-ledger rate: " << inLedgerRate
150 <<
"; TakerPays: " << reducedTakerPays
151 <<
"; TakerGets: " << reducedTakerGets
152 <<
"; bob already got: " << bobGot <<
std::endl;
155 inLedgerRate > initialRate ?
"**" :
" ";
156 std::cout <<
"| `" << reducedTakerGets <<
"` | `"
157 << reducedTakerPays <<
"` | `" << initialRate
158 <<
"` | " << filler <<
"`" << inLedgerRate <<
"`"
175 unsigned int blockedCount = 0;
177 mantissaReduce <= 5'000'000'000ull;
178 mantissaReduce += 20'000'000ull)
185 Amounts const aliceOffer{aliceUSD, aliceXRP};
186 blockedCount += exerciseOfferPair(aliceOffer, bobOffer);
191 BEAST_EXPECT(blockedCount == 0);
198 testcase(
"exercise partial cross old XRP/IOU offer Q change");
202 auto const gw =
Account{
"gateway"};
203 auto const alice =
Account{
"alice"};
204 auto const bob =
Account{
"bob"};
205 auto const usd = gw[
"USD"];
210 env.
fund(
XRP(10'000'000), gw, alice, bob);
213 env(
trust(alice, usd(10'000'000)));
214 env(
trust(bob, usd(10'000'000)));
217 env(
pay(gw, alice, usd(10'000'000)));
224 auto exerciseOfferPair = [
this, &env, &alice, &bob](
226 Amounts const& newOffer) ->
unsigned int {
231 env(
offer(alice, inLedger.in, inLedger.out));
237 env(
offer(bob, newOffer.in, newOffer.out));
251 unsigned int badRate = 1;
256 amountFromJson(sfTakerGets, aliceOffer[jss::node][sfTakerGets.jsonName]);
258 amountFromJson(sfTakerPays, aliceOffer[jss::node][sfTakerPays.jsonName]);
260 BEAST_EXPECT(reducedTakerPays < inLedger.in);
261 BEAST_EXPECT(reducedTakerGets < inLedger.out);
264 badRate = inLedgerRate > initialRate ? 1 : 0;
273 STAmount const tweakedTakerPays = reducedTakerPays +
drops(1);
276 BEAST_EXPECT(tweakedRate > initialRate);
279 std::cout <<
"Placed rate: " << initialRate
280 <<
"; in-ledger rate: " << inLedgerRate
281 <<
"; TakerPays: " << reducedTakerPays
282 <<
"; TakerGets: " << reducedTakerGets
283 <<
"; alice already got: " << aliceGot
287 std::cout <<
"| `" << reducedTakerGets <<
"` | `"
288 << reducedTakerPays <<
"` | `" << initialRate
289 <<
"` | " << filler <<
"`" << inLedgerRate <<
"`"
290 << filler <<
" | `" << aliceGot <<
"` |"
307 unsigned int blockedCount = 0;
309 mantissaReduce <= 4'000'000'000ull;
310 mantissaReduce += 20'000'000ull)
317 Amounts const bobOffer{bobUSD, bobXRP};
319 blockedCount += exerciseOfferPair(aliceOffer, bobOffer);
324 BEAST_EXPECT(blockedCount == 0);
331 testcase(
"exercise underfunded XRP/IOU offer Q change");
336 auto const alice =
Account{
"alice"};
337 auto const bob =
Account{
"bob"};
339 auto const usd = gw[
"USD"];
344 env.
fund(
XRP(10000), alice, bob, gw);
346 env.
trust(usd(1000), alice, bob);
348 int blockedOrderBookCount = 0;
349 for (
STAmount initialBobUSD = usd(0.45); initialBobUSD <= usd(1);
350 initialBobUSD += usd(0.025))
353 env(
pay(gw, bob, initialBobUSD));
372 bool const bobOfferGone = !
offerInLedger(env, bob, bobOfferSeq);
376 if (aliceBalanceUSD.
signum() > 0)
378 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
379 BEAST_EXPECT(env.
balance(bob, usd) == usd(0));
380 BEAST_EXPECT(bobOfferGone);
384 if (!bobOfferGone && aliceBalanceUSD.
signum() == 0)
386 ++blockedOrderBookCount;
395 aliceBalance.
signum() > 0)
396 env(
pay(alice, gw, aliceBalance));
399 env(
pay(bob, gw, bobBalance));
407 BEAST_EXPECT(blockedOrderBookCount == 0);
414 testcase(
"exercise underfunded IOU/IOU offer Q change");
419 using namespace std::chrono_literals;
420 auto const alice =
Account{
"alice"};
421 auto const bob =
Account{
"bob"};
424 auto const usd = gw[
"USD"];
425 auto const eur = gw[
"EUR"];
427 STAmount const tinyUSD(usd, 1, -81);
432 env.
fund(
XRP(10000), alice, bob, gw);
434 env.
trust(usd(1000), alice, bob);
435 env.
trust(eur(1000), alice, bob);
437 STAmount const eurOffer(eur, 2957, -76);
438 STAmount const usdOffer(usd, 7109, -76);
440 STAmount const endLoop(usd, 50, -81);
442 int blockedOrderBookCount = 0;
443 for (
STAmount initialBobUSD = tinyUSD; initialBobUSD <= endLoop;
444 initialBobUSD += tinyUSD)
447 env(
pay(gw, bob, initialBobUSD));
448 env(
pay(gw, alice, eur(100)));
453 env(
offer(bob, eurOffer, usdOffer));
459 env(
offer(alice, usdOffer, eurOffer));
464 bool const bobOfferGone = !
offerInLedger(env, bob, bobOfferSeq);
468 <<
"bob initial: " << initialBobUSD
469 <<
"; alice final: " << aliceBalanceUSD
470 <<
"; bob offer: " << bobOfferJson.toStyledString()
474 if (aliceBalanceUSD.
signum() > 0)
476 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
477 BEAST_EXPECT(env.
balance(bob, usd) == usd(0));
478 BEAST_EXPECT(bobOfferGone);
482 if (!bobOfferGone && aliceBalanceUSD.
signum() == 0)
484 ++blockedOrderBookCount;
493 auto zeroBalance = [&env, &gw](
Account const& acct,
IOU const& iou) {
495 env(
pay(acct, gw, balance));
498 zeroBalance(alice, eur);
499 zeroBalance(alice, usd);
500 zeroBalance(bob, eur);
501 zeroBalance(bob, usd);
507 BEAST_EXPECT(blockedOrderBookCount == 0);
526 testcase(
"exercise tfSell partial cross old XRP/IOU offer Q change");
534 auto const usd = gw[
"USD"];
541 Env env{*
this, features};
542 env.
fund(
XRP(10'000'000), gw, alice, bob, carol);
545 env(
trust(alice, usd(10'000'000)));
546 env(
trust(bob, usd(10'000'000)));
547 env(
trust(carol, usd(10'000'000)));
550 env(
pay(gw, alice, usd(10'000'000)));
551 env(
pay(gw, bob, usd(10'000'000)));
552 env(
pay(gw, carol, usd(10'000'000)));
559 auto exerciseOfferTrio = [
this, &env, &alice, &bob, &carol, &usd](
560 Amounts const& carolOffer) ->
unsigned int {
563 static Amounts const kAliceInitialOffer(usd(2),
drops(3382562));
564 env(
offer(alice, kAliceInitialOffer.
in, kAliceInitialOffer.
out));
567 env, alice, aliceOfferSeq)[jss::node]))
572 env(
offer(bob, usd(0.97086565812384),
drops(1642020)));
578 env(
offer(carol, carolOffer.in, carolOffer.out),
Txflags(tfSell));
590 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}, {carol, carolOfferSeq}});
595 unsigned int badRate = 1;
601 BEAST_EXPECT(aliceReducedOffer.
in < kAliceInitialOffer.
in);
602 BEAST_EXPECT(aliceReducedOffer.
out < kAliceInitialOffer.
out);
604 badRate = inLedgerRate > initialRate ? 1 : 0;
620 BEAST_EXPECT(tweakedRate > initialRate);
623 std::cout <<
"Placed rate: " << initialRate
624 <<
"; in-ledger rate: " << inLedgerRate
625 <<
"; TakerPays: " << aliceReducedOffer.
in
626 <<
"; TakerGets: " << aliceReducedOffer.
out
630 std::cout <<
"| " << aliceReducedOffer.
in <<
"` | `"
631 << aliceReducedOffer.
out <<
"` | `" << initialRate
632 <<
"` | " << filler <<
"`" << inLedgerRate <<
"`"
640 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}, {carol, carolOfferSeq}});
644 static constexpr int kLoopCount = 100;
645 unsigned int blockedCount = 0;
649 for (
unsigned int i = 0; i < kLoopCount; ++i)
652 exerciseOfferTrio(
Amounts(
drops(1642020), usd(1) + increaseGets));
653 increaseGets += step;
662 if (features[fixReducedOffersV2])
664 BEAST_EXPECT(blockedCount == 0);
668 BEAST_EXPECT(blockedCount > 80);
TestcaseT testcase
Memberspace for declaring test cases.
bool isMember(char const *key) const
Return true if the object has a member named key.
Represents the logical ratio of output currency to input currency.
STAmount rate() const
Returns the quality as STAmount.
std::uint64_t mantissa() const noexcept
int signum() const noexcept
bool negative() const noexcept
Asset const & asset() const
int exponent() const noexcept
static auto ledgerEntryOffer(jtx::Env &env, jtx::Account const &acct, std::uint32_t offerSeq)
void testPartialCrossNewXrpIouQChange()
void testSellPartialCrossOldXrpIouQChange()
void testUnderFundedIouIouQChange()
void run() override
Runs the suite.
static bool offerInLedger(jtx::Env &env, jtx::Account const &acct, std::uint32_t offerSeq)
static Amounts jsonOfferToAmounts(json::Value const &json)
void testUnderFundedXrpIouQChange()
static void cleanupOldOffers(jtx::Env &env, std::initializer_list< std::pair< jtx::Account const &, std::uint32_t > > list)
void testPartialCrossOldXrpIouQChange()
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.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
void require(Args const &... args)
Check a set of requirements.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Converts to IOU Issue or STAmount.
JSON (JavaScript Object Notation).
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
json::Value offerCancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
XrpT const XRP
Converts to XRP Issue or STAmount.
FeatureBitset testableAmendments()
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
OwnerCount< ltOFFER > offers
Match the number of offers in the account's owner directory.
json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
TAmounts< STAmount, STAmount > Amounts
std::string to_string(BaseUInt< Bits, Tag > const &a)
STAmount amountFromJson(SField const &name, json::Value const &v)