xrpld
Loading...
Searching...
No Matches
CrossingLimits_test.cpp
1
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/balance.h>
7#include <test/jtx/offer.h>
8#include <test/jtx/owners.h>
9#include <test/jtx/pay.h>
10#include <test/jtx/tags.h>
11#include <test/jtx/ter.h>
12
13#include <xrpl/beast/unit_test/suite.h>
14#include <xrpl/protocol/Feature.h>
15#include <xrpl/protocol/TER.h>
16
17namespace xrpl::test {
18
20{
21public:
22 void
24 {
25 testcase("Step Limit");
26
27 using namespace jtx;
28 Env env(*this, features);
29
30 auto const gw = Account("gateway");
31 auto const usd = gw["USD"];
32
33 env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan");
34 env.trust(usd(1), "bob");
35 env(pay(gw, "bob", usd(1)));
36 env.trust(usd(1), "dan");
37 env(pay(gw, "dan", usd(1)));
38 nOffers(env, 2000, "bob", XRP(1), usd(1));
39 nOffers(env, 1, "dan", XRP(1), usd(1));
40
41 // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
42 // offer, removes 999 more as unfunded, then hits the step limit.
43 env(offer("alice", usd(1000), XRP(1000)));
44 env.require(Balance("alice", usd(1)));
45 env.require(Owners("alice", 2));
46 env.require(Balance("bob", usd(0)));
47 env.require(Owners("bob", 1001));
48 env.require(Balance("dan", usd(1)));
49 env.require(Owners("dan", 2));
50
51 // Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
52 // 1000 offers as unfunded and hits the step limit.
53 env(offer("carol", usd(1000), XRP(1000)));
54 env.require(Balance("carol", usd(kNone)));
55 env.require(Owners("carol", 1));
56 env.require(Balance("bob", usd(0)));
57 env.require(Owners("bob", 1));
58 env.require(Balance("dan", usd(1)));
59 env.require(Owners("dan", 2));
60 }
61
62 void
64 {
65 testcase("Crossing Limit");
66
67 using namespace jtx;
68 Env env(*this, features);
69
70 auto const gw = Account("gateway");
71 auto const usd = gw["USD"];
72
73 // The payment engine allows 1000 offers to cross.
74 int const maxConsumed = 1000;
75
76 env.fund(XRP(100000000), gw, "alice", "bob", "carol");
77 int const bobOfferCount = maxConsumed + 150;
78 env.trust(usd(bobOfferCount), "bob");
79 env(pay(gw, "bob", usd(bobOfferCount)));
80 env.close();
81 nOffers(env, bobOfferCount, "bob", XRP(1), usd(1));
82
83 // Alice offers to buy Bob's offers. However she hits the offer
84 // crossing limit, so she can't buy them all at once.
85 env(offer("alice", usd(bobOfferCount), XRP(bobOfferCount)));
86 env.close();
87 env.require(Balance("alice", usd(maxConsumed)));
88 env.require(Balance("bob", usd(150)));
89 env.require(Owners("bob", 150 + 1));
90
91 // Carol offers to buy 1000 XRP for 1000 USD. She takes Bob's
92 // remaining 150 offers without hitting a limit.
93 env(offer("carol", usd(1000), XRP(1000)));
94 env.close();
95 env.require(Balance("carol", usd(150)));
96 env.require(Balance("bob", usd(0)));
97 env.require(Owners("bob", 1));
98 }
99
100 void
102 {
103 testcase("Step And Crossing Limit");
104
105 using namespace jtx;
106 Env env(*this, features);
107
108 auto const gw = Account("gateway");
109 auto const usd = gw["USD"];
110
111 env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan", "evita");
112
113 // The payment engine allows 1000 offers to cross.
114 int const maxConsumed = 1000;
115
116 int const evitaOfferCount{maxConsumed + 49};
117 env.trust(usd(1000), "alice");
118 env(pay(gw, "alice", usd(1000)));
119 env.trust(usd(1000), "carol");
120 env(pay(gw, "carol", usd(1)));
121 env.trust(usd(evitaOfferCount + 1), "evita");
122 env(pay(gw, "evita", usd(evitaOfferCount + 1)));
123
124 // The payment engine has a limit of 1000 funded or unfunded offers.
125 int const carolsOfferCount{700};
126 nOffers(env, 400, "alice", XRP(1), usd(1));
127 nOffers(env, carolsOfferCount, "carol", XRP(1), usd(1));
128 nOffers(env, evitaOfferCount, "evita", XRP(1), usd(1));
129
130 // Bob offers to buy 1000 XRP for 1000 USD. He takes all 400 USD from
131 // Alice's offers, 1 USD from Carol's and then removes 599 of Carol's
132 // offers as unfunded, before hitting the step limit.
133 env(offer("bob", usd(1000), XRP(1000)));
134 env.require(Balance("bob", usd(401)));
135 env.require(Balance("alice", usd(600)));
136 env.require(Owners("alice", 1));
137 env.require(Balance("carol", usd(0)));
138 env.require(Owners("carol", carolsOfferCount - 599));
139 env.require(Balance("evita", usd(evitaOfferCount + 1)));
140 env.require(Owners("evita", evitaOfferCount + 1));
141
142 // Dan offers to buy maxConsumed + 50 XRP USD. He removes all of
143 // Carol's remaining offers as unfunded, then takes
144 // (maxConsumed - 100) USD from Evita's, hitting the crossing limit.
145 env(offer("dan", usd(maxConsumed + 50), XRP(maxConsumed + 50)));
146 env.require(Balance("dan", usd(maxConsumed - 100)));
147 env.require(Owners("dan", 2));
148 env.require(Balance("alice", usd(600)));
149 env.require(Owners("alice", 1));
150 env.require(Balance("carol", usd(0)));
151 env.require(Owners("carol", 1));
152 env.require(Balance("evita", usd(150)));
153 env.require(Owners("evita", 150));
154 }
155
156 void
158 {
159 testcase("Auto Bridged Limits");
160
161 // Extracts as much as possible in one book at one Quality
162 // before proceeding to the other book. This reduces the number of
163 // times we change books.
164
165 // If any book step in a payment strand consumes 1000 offers, the
166 // liquidity from the offers is used, but that strand will be marked as
167 // dry for the remainder of the transaction.
168
169 using namespace jtx;
170
171 auto const gw = Account("gateway");
172 auto const alice = Account("alice");
173 auto const bob = Account("bob");
174 auto const carol = Account("carol");
175
176 auto const usd = gw["USD"];
177 auto const eur = gw["EUR"];
178
179 // There are two almost identical tests. There is a strand with a large
180 // number of unfunded offers that will cause the strand to be marked dry
181 // even though there will still be liquidity available on that strand.
182 // In the first test, the strand has the best initial quality. In the
183 // second test the strand does not have the best quality (the
184 // implementation has to handle this case correct and not mark the
185 // strand dry until the liquidity is actually used)
186
187 // The implementation allows any single step to consume at most 1000
188 // offers.If the total number of offers consumed by all the steps
189 // combined exceeds 1500, the payment stops.
190 {
191 Env env(*this, features);
192
193 env.fund(XRP(100000000), gw, alice, bob, carol);
194
195 env.trust(usd(4000), alice);
196 env(pay(gw, alice, usd(4000)));
197 env.trust(usd(1000), carol);
198 env(pay(gw, carol, usd(3)));
199
200 // Notice the strand with the 800 unfunded offers has the initial
201 // best quality
202 nOffers(env, 2000, alice, eur(2), XRP(1));
203 nOffers(env, 100, alice, XRP(1), usd(4));
204 nOffers(env, 801, carol, XRP(1), usd(3)); // only one offer is funded
205 nOffers(env, 1000, alice, XRP(1), usd(3));
206
207 nOffers(env, 1, alice, eur(500), usd(500));
208
209 // Bob offers to buy 2000 USD for 2000 EUR; He starts with 2000 EUR
210 // 1. The best quality is the autobridged offers that take 2 EUR
211 // and give 4 USD.
212 // Bob spends 200 EUR and receives 400 USD.
213 // 100 EUR->XRP offers consumed.
214 // 100 XRP->USD offers consumed.
215 // 200 total offers consumed.
216 //
217 // 2. The best quality is the autobridged offers that take 2 EUR
218 // and give 3 USD.
219 // a. One of Carol's offers is taken. This leaves her other
220 // offers unfunded.
221 // b. Carol's remaining 800 offers are consumed as unfunded.
222 // c. 199 of alice's XRP(1) to USD(3) offers are consumed.
223 // A book step is allowed to consume a maximum of 1000 offers
224 // at a given quality, and that limit is now reached.
225 // d. Now the strand is dry, even though there are still funded
226 // XRP(1) to USD(3) offers available.
227 // Bob has spent 400 EUR and received 600 USD in this step.
228 // 200 EUR->XRP offers consumed
229 // 800 unfunded XRP->USD offers consumed
230 // 200 funded XRP->USD offers consumed (1 carol, 199 alice)
231 // 1400 total offers consumed so far (100 left before the
232 // limit)
233 // 3. The best is the non-autobridged offers that takes 500 EUR and
234 // gives 500 USD.
235 // Bob started with 2000 EUR
236 // Bob spent 500 EUR (100+400)
237 // Bob has 1500 EUR left
238 // In this step:
239 // Bob spents 500 EUR and receives 500 USD.
240 // In total:
241 // Bob spent 1100 EUR (200 + 400 + 500)
242 // Bob has 900 EUR remaining (2000 - 1100)
243 // Bob received 1500 USD (400 + 600 + 500)
244 // Alice spent 1497 USD (100*4 + 199*3 + 500)
245 // Alice has 2503 remaining (4000 - 1497)
246 // Alice received 1100 EUR (200 + 400 + 500)
247 env.trust(eur(10000), bob);
248 env.close();
249 env(pay(gw, bob, eur(2000)));
250 env.close();
251 env(offer(bob, usd(4000), eur(4000)));
252 env.close();
253
254 env.require(Balance(bob, usd(1500)));
255 env.require(Balance(bob, eur(900)));
256 env.require(offers(bob, 1));
257 env.require(Owners(bob, 3));
258
259 env.require(Balance(alice, usd(2503)));
260 env.require(Balance(alice, eur(1100)));
261 auto const numAOffers = 2000 + 100 + 1000 + 1 - ((2 * 100) + (2 * 199) + 1 + 1);
262 env.require(offers(alice, numAOffers));
263 env.require(Owners(alice, numAOffers + 2));
264
265 env.require(offers(carol, 0));
266 }
267 {
268 Env env(*this, features);
269
270 env.fund(XRP(100000000), gw, alice, bob, carol);
271
272 env.trust(usd(4000), alice);
273 env(pay(gw, alice, usd(4000)));
274 env.trust(usd(1000), carol);
275 env(pay(gw, carol, usd(3)));
276
277 // Notice the strand with the 800 unfunded offers does not have the
278 // initial best quality
279 nOffers(env, 1, alice, eur(1), usd(10));
280 nOffers(env, 2000, alice, eur(2), XRP(1));
281 nOffers(env, 100, alice, XRP(1), usd(4));
282 nOffers(env, 801, carol, XRP(1), usd(3)); // only one offer is funded
283 nOffers(env, 1000, alice, XRP(1), usd(3));
284
285 nOffers(env, 1, alice, eur(499), usd(499));
286
287 // Bob offers to buy 2000 USD for 2000 EUR; He starts with 2000 EUR
288 // 1. The best quality is the offer that takes 1 EUR and gives 10
289 // USD
290 // Bob spends 1 EUR and receives 10 USD.
291 //
292 // 2. The best quality is the autobridged offers that takes 2 EUR
293 // and gives 4 USD.
294 // Bob spends 200 EUR and receives 400 USD.
295 //
296 // 3. The best quality is the autobridged offers that takes 2 EUR
297 // and gives 3 USD.
298 // a. One of Carol's offers is taken. This leaves her other
299 // offers unfunded.
300 // b. Carol's remaining 800 offers are consumed as unfunded.
301 // c. 199 of alice's XRP(1) to USD(3) offers are consumed.
302 // A book step is allowed to consume a maximum of 1000 offers
303 // at a given quality, and that limit is now reached.
304 // d. Now the strand is dry, even though there are still funded
305 // XRP(1) to USD(3) offers available. Bob has spent 400 EUR and
306 // received 600 USD in this step. (200 funded offers consumed
307 // 800 unfunded offers)
308 // 4. The best is the non-autobridged offers that takes 499 EUR and
309 // gives 499 USD.
310 // Bob has 2000 EUR, and has spent 1+200+400=601 EUR. He has
311 // 1399 left. Bob spent 499 EUR and receives 499 USD.
312 // In total: Bob spent EUR(1 + 200 + 400 + 499) = EUR(1100). He
313 // started with 2000 so has 900 remaining
314 // Bob received USD(10 + 400 + 600 + 499) = USD(1509).
315 // Alice spent 10 + 100*4 + 199*3 + 499 = 1506 USD. She
316 // started with 4000 so has 2494 USD remaining. Alice
317 // received 200 + 400 + 500 = 1100 EUR
318 env.trust(eur(10000), bob);
319 env.close();
320 env(pay(gw, bob, eur(2000)));
321 env.close();
322 env(offer(bob, usd(4000), eur(4000)));
323 env.close();
324
325 env.require(Balance(bob, usd(1509)));
326 env.require(Balance(bob, eur(900)));
327 env.require(offers(bob, 1));
328 env.require(Owners(bob, 3));
329
330 env.require(Balance(alice, usd(2494)));
331 env.require(Balance(alice, eur(1100)));
332 auto const numAOffers = 1 + 2000 + 100 + 1000 + 1 - (1 + (2 * 100) + (2 * 199) + 1 + 1);
333 env.require(offers(alice, numAOffers));
334 env.require(Owners(alice, numAOffers + 2));
335
336 env.require(offers(carol, 0));
337 }
338 }
339
340 void
342 {
343 testcase("Offer Overflow");
344
345 using namespace jtx;
346
347 auto const gw = Account("gateway");
348 auto const alice = Account("alice");
349 auto const bob = Account("bob");
350
351 auto const usd = gw["USD"];
352
353 Env env(*this, features);
354
355 env.fund(XRP(100000000), gw, alice, bob);
356
357 env.trust(usd(8000), alice);
358 env.trust(usd(8000), bob);
359 env.close();
360
361 env(pay(gw, alice, usd(8000)));
362 env.close();
363
364 // The new flow cross handles consuming excessive offers differently
365 // than the old offer crossing code. In the old code, the total number
366 // of consumed offers is tracked, and the crossings will stop after this
367 // limit is hit. In the new code, the number of offers is tracked per
368 // offerbook and per quality. This test shows how they can differ. Set
369 // up a book with many offers. At each quality keep the number of offers
370 // below the limit. However, if all the offers are consumed it would
371 // create a tecOVERSIZE error.
372
373 // The implementation allows any single step to consume at most 1000
374 // offers. If the total number of offers consumed by all the steps
375 // combined exceeds 1500, the payment stops. Since the first set of
376 // offers consumes 998 offers, the second set will consume 998, which is
377 // not over the limit and the payment stops. So 2*998, or 1996 is the
378 // expected value.
379 nOffers(env, 998, alice, XRP(1.00), usd(1));
380 nOffers(env, 998, alice, XRP(0.99), usd(1));
381 nOffers(env, 998, alice, XRP(0.98), usd(1));
382 nOffers(env, 998, alice, XRP(0.97), usd(1));
383 nOffers(env, 998, alice, XRP(0.96), usd(1));
384 nOffers(env, 998, alice, XRP(0.95), usd(1));
385
386 env(offer(bob, usd(8000), XRP(8000)), Ter(tesSUCCESS));
387 env.close();
388
389 env.require(Balance(bob, usd(1996)));
390 }
391
392 void
393 run() override
394 {
395 auto testAll = [this](FeatureBitset features) {
396 testStepLimit(features);
397 testCrossingLimit(features);
398 testStepAndCrossingLimit(features);
399 testAutoBridgedLimits(features);
400 testOfferOverflow(features);
401 };
402 using namespace jtx;
403 auto const sa = testableAmendments();
404 testAll(sa);
405 testAll(sa - featurePermissionedDEX);
406 }
407};
408
409BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(CrossingLimits, app, xrpl, 10);
410
411} // namespace xrpl::test
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
void run() override
Runs the suite.
void testStepAndCrossingLimit(FeatureBitset features)
void testOfferOverflow(FeatureBitset features)
void testAutoBridgedLimits(FeatureBitset features)
void testStepLimit(FeatureBitset features)
void testCrossingLimit(FeatureBitset features)
A transaction testing environment.
Definition Env.h:143
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:296
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:327
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:605
Match the number of items in the account's owner directory.
Definition owners.h:52
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:13
void nOffers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
static NoneT const kNone
Definition tags.h:9
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:76
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
OwnerCount< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition owners.h:70
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(CrossingLimits, app, xrpl, 10)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ tesSUCCESS
Definition TER.h:240