xrpld
Loading...
Searching...
No Matches
VaultScale_test.cpp
1#include <test/app/vault/VaultTestBase.h>
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/fee.h>
7#include <test/jtx/flags.h>
8#include <test/jtx/mpt.h>
9#include <test/jtx/pay.h>
10#include <test/jtx/sig.h>
11#include <test/jtx/ter.h>
12#include <test/jtx/utility.h>
13#include <test/jtx/vault.h>
14
15#include <xrpl/basics/Number.h>
16#include <xrpl/beast/unit_test/suite.h>
17#include <xrpl/beast/utility/Journal.h>
18#include <xrpl/core/ServiceRegistry.h>
19#include <xrpl/json/json_forwards.h>
20#include <xrpl/json/json_value.h>
21#include <xrpl/ledger/ApplyView.h>
22#include <xrpl/ledger/OpenView.h>
23#include <xrpl/ledger/Sandbox.h>
24#include <xrpl/protocol/Asset.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/Issue.h>
28#include <xrpl/protocol/Keylet.h>
29#include <xrpl/protocol/MPTIssue.h>
30#include <xrpl/protocol/SField.h>
31#include <xrpl/protocol/STAmount.h>
32#include <xrpl/protocol/SeqProxy.h>
33#include <xrpl/protocol/SystemParameters.h>
34#include <xrpl/protocol/TER.h>
35#include <xrpl/protocol/TxFlags.h>
36#include <xrpl/protocol/UintTypes.h>
37#include <xrpl/protocol/Units.h>
38#include <xrpl/protocol/XRPAmount.h>
39
40#include <cstdint>
41#include <exception>
42#include <functional>
43#include <limits>
44#include <string>
45#include <tuple>
46#include <utility>
47
48namespace xrpl {
49
51{
52private:
53 void
55 {
56 using namespace test::jtx;
57
58 struct Data
59 {
60 Account const& owner;
61 Account const& issuer;
62 Account const& depositor;
63 Account const& vaultAccount;
64 MPTIssue shares;
65 PrettyAsset const& share;
66 Vault& vault;
68 Issue assets;
69 PrettyAsset const& asset;
70 std::function<bool(std::function<bool(SLE&, SLE&)>)> peek;
71 };
72
73 auto testCase = [&, this](
74 std::uint8_t scale, std::function<void(Env & env, Data data)> test) {
75 // These scale-focused tests build an open-ended vault and
76 // exercise deposit/withdraw/clawback (with one test also
77 // attaching a loan broker). featureLendingProtocolV1_1 adds a
78 // closed-ended vault gate on LoanBrokerSet::preclaim and is
79 // orthogonal to what this suite asserts, so strip it here.
80 Env env{*this, testableAmendments() - featureLendingProtocolV1_1};
81 Account const owner{"owner"};
82 Account const issuer{"issuer"};
83 Account const depositor{"depositor"};
84 Vault vault{env};
85 env.fund(XRP(1000), issuer, owner, depositor);
86 env(fset(issuer, asfAllowTrustLineClawback));
87 env.close();
88
89 PrettyAsset const asset = issuer["IOU"];
90 env.trust(asset(1000), owner);
91 env.trust(asset(1000), depositor);
92 env(pay(issuer, owner, asset(200)));
93 env(pay(issuer, depositor, asset(200)));
94 env.close();
95
96 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
97 tx[sfScale] = scale;
98 env(tx);
99
100 auto const [vaultAccount, issuanceId] =
102 auto const vault = env.le(keylet);
103 return {Account("vault", vault->at(sfAccount)), vault->at(sfShareMPTID)};
104 }(keylet);
105 MPTIssue const shares(issuanceId);
106 env.memoize(vaultAccount);
107
108 auto const peek = [keylet, &env, this](std::function<bool(SLE&, SLE&)> fn) -> bool {
109 return env.app().getOpenLedger().modify(
110 [&](OpenView& view, beast::Journal j) -> bool {
111 Sandbox sb(&view, TapNone);
112 auto vault = sb.peek(keylet::vault(keylet.key));
113 if (!BEAST_EXPECT(vault))
114 return false;
115 auto shares = sb.peek(keylet::mptokenIssuance(vault->at(sfShareMPTID)));
116 if (!BEAST_EXPECT(shares))
117 return false;
118 if (fn(*vault, *shares))
119 {
120 sb.update(vault);
121 sb.update(shares);
122 sb.apply(view);
123 return true;
124 }
125 return false;
126 });
127 };
128
129 test(
130 env,
131 {.owner = owner,
132 .issuer = issuer,
133 .depositor = depositor,
134 .vaultAccount = vaultAccount,
135 .shares = shares,
136 .share = PrettyAsset(shares),
137 .vault = vault,
138 .keylet = keylet,
139 .assets = asset.raw().get<Issue>(),
140 .asset = asset,
141 .peek = peek});
142 };
143
144 testCase(18, [&, this](Env& env, Data d) {
145 testcase("Scale deposit overflow on first deposit");
146 auto tx = d.vault.deposit(
147 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(10)});
148 env(tx, Ter{tecPATH_DRY});
149 env.close();
150 });
151
152 testCase(18, [&, this](Env& env, Data d) {
153 testcase("Scale deposit overflow on second deposit");
154
155 {
156 auto tx = d.vault.deposit(
157 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
158 env(tx);
159 env.close();
160 }
161
162 {
163 auto tx = d.vault.deposit(
164 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(10)});
165 env(tx, Ter{tecPATH_DRY});
166 env.close();
167 }
168 });
169
170 testCase(18, [&, this](Env& env, Data d) {
171 testcase("Scale deposit overflow on total shares");
172
173 {
174 auto tx = d.vault.deposit(
175 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
176 env(tx);
177 env.close();
178 }
179
180 {
181 auto tx = d.vault.deposit(
182 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
183 env(tx, Ter{tecPATH_DRY});
184 env.close();
185 }
186 });
187
188 testCase(1, [&, this](Env& env, Data d) {
189 testcase("Scale deposit exact");
190
191 auto const start = env.balance(d.depositor, d.assets).number();
192 auto tx = d.vault.deposit(
193 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(1)});
194 env(tx);
195 env.close();
196 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(10));
197 BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start - 1));
198 });
199
200 testCase(1, [&, this](Env& env, Data d) {
201 testcase("Scale deposit insignificant amount");
202
203 auto tx = d.vault.deposit(
204 {.depositor = d.depositor,
205 .id = d.keylet.key,
206 .amount = STAmount(d.asset, Number(9, -2))});
207 env(tx, Ter{tecPRECISION_LOSS});
208 });
209
210 testCase(1, [&, this](Env& env, Data d) {
211 testcase("Scale deposit exact, using full precision");
212
213 auto const start = env.balance(d.depositor, d.assets).number();
214 auto tx = d.vault.deposit(
215 {.depositor = d.depositor,
216 .id = d.keylet.key,
217 .amount = STAmount(d.asset, Number(15, -1))});
218 env(tx);
219 env.close();
220 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(15));
221 BEAST_EXPECT(
222 env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(15, -1)));
223 });
224
225 testCase(1, [&, this](Env& env, Data d) {
226 testcase("Scale deposit exact, truncating from .5");
227
228 auto const start = env.balance(d.depositor, d.assets).number();
229 // Each of the cases below will transfer exactly 1.2 IOU to the
230 // vault and receive 12 shares in exchange
231 {
232 auto tx = d.vault.deposit(
233 {.depositor = d.depositor,
234 .id = d.keylet.key,
235 .amount = STAmount(d.asset, Number(125, -2))});
236 env(tx);
237 env.close();
238 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
239 BEAST_EXPECT(
240 env.balance(d.depositor, d.assets) ==
241 STAmount(d.asset, start - Number(12, -1)));
242 }
243
244 {
245 auto tx = d.vault.deposit(
246 {.depositor = d.depositor,
247 .id = d.keylet.key,
248 .amount = STAmount(d.asset, Number(1201, -3))});
249 env(tx);
250 env.close();
251 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(24));
252 BEAST_EXPECT(
253 env.balance(d.depositor, d.assets) ==
254 STAmount(d.asset, start - Number(24, -1)));
255 }
256
257 {
258 auto tx = d.vault.deposit(
259 {.depositor = d.depositor,
260 .id = d.keylet.key,
261 .amount = STAmount(d.asset, Number(1299, -3))});
262 env(tx);
263 env.close();
264 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(36));
265 BEAST_EXPECT(
266 env.balance(d.depositor, d.assets) ==
267 STAmount(d.asset, start - Number(36, -1)));
268 }
269 });
270
271 testCase(1, [&, this](Env& env, Data d) {
272 testcase("Scale deposit exact, truncating from .01");
273
274 auto const start = env.balance(d.depositor, d.assets).number();
275 // round to 12
276 auto tx = d.vault.deposit(
277 {.depositor = d.depositor,
278 .id = d.keylet.key,
279 .amount = STAmount(d.asset, Number(1201, -3))});
280 env(tx);
281 env.close();
282 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
283 BEAST_EXPECT(
284 env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(12, -1)));
285
286 {
287 // round to 6
288 auto tx = d.vault.deposit(
289 {.depositor = d.depositor,
290 .id = d.keylet.key,
291 .amount = STAmount(d.asset, Number(69, -2))});
292 env(tx);
293 env.close();
294 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
295 BEAST_EXPECT(
296 env.balance(d.depositor, d.assets) ==
297 STAmount(d.asset, start - Number(18, -1)));
298 }
299 });
300
301 testCase(1, [&, this](Env& env, Data d) {
302 testcase("Scale deposit exact, truncating from .99");
303
304 auto const start = env.balance(d.depositor, d.assets).number();
305 // round to 12
306 auto tx = d.vault.deposit(
307 {.depositor = d.depositor,
308 .id = d.keylet.key,
309 .amount = STAmount(d.asset, Number(1299, -3))});
310 env(tx);
311 env.close();
312 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
313 BEAST_EXPECT(
314 env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(12, -1)));
315
316 {
317 // round to 6
318 auto tx = d.vault.deposit(
319 {.depositor = d.depositor,
320 .id = d.keylet.key,
321 .amount = STAmount(d.asset, Number(62, -2))});
322 env(tx);
323 env.close();
324 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
325 BEAST_EXPECT(
326 env.balance(d.depositor, d.assets) ==
327 STAmount(d.asset, start - Number(18, -1)));
328 }
329 });
330
331 testCase(1, [&, this](Env& env, Data d) {
332 // initial setup: deposit 100 IOU, receive 1000 shares
333 auto const start = env.balance(d.depositor, d.assets).number();
334 auto tx = d.vault.deposit(
335 {.depositor = d.depositor,
336 .id = d.keylet.key,
337 .amount = STAmount(d.asset, Number(100, 0))});
338 env(tx);
339 env.close();
340 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
341 BEAST_EXPECT(
342 env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(100, 0)));
343 BEAST_EXPECT(
344 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(100, 0)));
345 BEAST_EXPECT(
346 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-1000, 0)));
347
348 {
349 testcase("Scale redeem exact");
350 // sharesToAssetsWithdraw:
351 // assets = assetsTotal * (shares / sharesTotal)
352 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
353
354 auto const start = env.balance(d.depositor, d.assets).number();
355 auto tx = d.vault.withdraw(
356 {.depositor = d.depositor,
357 .id = d.keylet.key,
358 .amount = STAmount(d.share, Number(100, 0))});
359 env(tx);
360 env.close();
361 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900));
362 BEAST_EXPECT(
363 env.balance(d.depositor, d.assets) == STAmount(d.asset, start + Number(10, 0)));
364 BEAST_EXPECT(
365 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(90, 0)));
366 BEAST_EXPECT(
367 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-900, 0)));
368 }
369
370 {
371 testcase("Scale redeem with rounding");
372 // sharesToAssetsWithdraw:
373 // assets = assetsTotal * (shares / sharesTotal)
374 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
375
376 auto const start = env.balance(d.depositor, d.assets).number();
377 d.peek([](SLE& vault, auto&) -> bool {
378 vault[sfAssetsAvailable] = Number(1);
379 return true;
380 });
381
382 // Note, this transaction fails first (because of above change
383 // in the open ledger) but then succeeds when the ledger is
384 // closed (because a modification like above is not persistent),
385 // which is why the checks below are expected to pass.
386 auto tx = d.vault.withdraw(
387 {.depositor = d.depositor,
388 .id = d.keylet.key,
389 .amount = STAmount(d.share, Number(25, 0))});
390 env(tx, Ter{tecINSUFFICIENT_FUNDS});
391 env.close();
392 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900 - 25));
393 BEAST_EXPECT(
394 env.balance(d.depositor, d.assets) ==
395 STAmount(d.asset, start + Number(25, -1)));
396 BEAST_EXPECT(
397 env.balance(d.vaultAccount, d.assets) ==
398 STAmount(d.asset, Number(900 - 25, -1)));
399 BEAST_EXPECT(
400 env.balance(d.vaultAccount, d.shares) ==
401 STAmount(d.share, -Number(900 - 25, 0)));
402 }
403
404 {
405 testcase("Scale redeem exact");
406 // sharesToAssetsWithdraw:
407 // assets = assetsTotal * (shares / sharesTotal)
408 // assets = 87.5 * 21 / 875 = 87.5 * 0.024 = 2.1
409
410 auto const start = env.balance(d.depositor, d.assets).number();
411
412 tx = d.vault.withdraw(
413 {.depositor = d.depositor,
414 .id = d.keylet.key,
415 .amount = STAmount(d.share, Number(21, 0))});
416 env(tx);
417 env.close();
418 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(875 - 21));
419 BEAST_EXPECT(
420 env.balance(d.depositor, d.assets) ==
421 STAmount(d.asset, start + Number(21, -1)));
422 BEAST_EXPECT(
423 env.balance(d.vaultAccount, d.assets) ==
424 STAmount(d.asset, Number(875 - 21, -1)));
425 BEAST_EXPECT(
426 env.balance(d.vaultAccount, d.shares) ==
427 STAmount(d.share, -Number(875 - 21, 0)));
428 }
429
430 {
431 testcase("Scale redeem rest");
432 auto const rest = env.balance(d.depositor, d.shares).number();
433
434 tx = d.vault.withdraw(
435 {.depositor = d.depositor,
436 .id = d.keylet.key,
437 .amount = STAmount(d.share, rest)});
438 env(tx);
439 env.close();
440 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
441 BEAST_EXPECT(env.balance(d.vaultAccount, d.assets).number() == 0);
442 BEAST_EXPECT(env.balance(d.vaultAccount, d.shares).number() == 0);
443 }
444 });
445
446 testCase(18, [&, this](Env& env, Data d) {
447 testcase("Scale withdraw overflow");
448
449 {
450 auto tx = d.vault.deposit(
451 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
452 env(tx);
453 env.close();
454 }
455
456 {
457 auto tx = d.vault.withdraw(
458 {.depositor = d.depositor,
459 .id = d.keylet.key,
460 .amount = STAmount(d.asset, Number(10, 0))});
461 env(tx, Ter{tecPATH_DRY});
462 env.close();
463 }
464 });
465
466 testCase(1, [&, this](Env& env, Data d) {
467 // initial setup: deposit 100 IOU, receive 1000 shares
468 auto const start = env.balance(d.depositor, d.assets).number();
469 auto tx = d.vault.deposit(
470 {.depositor = d.depositor,
471 .id = d.keylet.key,
472 .amount = STAmount(d.asset, Number(100, 0))});
473 env(tx);
474 env.close();
475 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
476 BEAST_EXPECT(
477 env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(100, 0)));
478 BEAST_EXPECT(
479 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(100, 0)));
480 BEAST_EXPECT(
481 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-1000, 0)));
482
483 {
484 testcase("Scale withdraw exact");
485 // assetsToSharesWithdraw:
486 // shares = sharesTotal * (assets / assetsTotal)
487 // shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
488 // sharesToAssetsWithdraw:
489 // assets = assetsTotal * (shares / sharesTotal)
490 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
491
492 auto const start = env.balance(d.depositor, d.assets).number();
493 auto tx = d.vault.withdraw(
494 {.depositor = d.depositor,
495 .id = d.keylet.key,
496 .amount = STAmount(d.asset, Number(10, 0))});
497 env(tx);
498 env.close();
499 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900));
500 BEAST_EXPECT(
501 env.balance(d.depositor, d.assets) == STAmount(d.asset, start + Number(10, 0)));
502 BEAST_EXPECT(
503 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(90, 0)));
504 BEAST_EXPECT(
505 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, Number(-900, 0)));
506 }
507
508 {
509 testcase("Scale withdraw insignificant amount");
510 auto tx = d.vault.withdraw(
511 {.depositor = d.depositor,
512 .id = d.keylet.key,
513 .amount = STAmount(d.asset, Number(4, -2))});
514 env(tx, Ter{tecPRECISION_LOSS});
515 }
516
517 {
518 testcase("Scale withdraw with rounding assets");
519 // assetsToSharesWithdraw:
520 // shares = sharesTotal * (assets / assetsTotal)
521 // shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
522 // sharesToAssetsWithdraw:
523 // assets = assetsTotal * (shares / sharesTotal)
524 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
525
526 auto const start = env.balance(d.depositor, d.assets).number();
527 d.peek([](SLE& vault, auto&) -> bool {
528 vault[sfAssetsAvailable] = Number(1);
529 return true;
530 });
531
532 // Note, this transaction fails first (because of above change
533 // in the open ledger) but then succeeds when the ledger is
534 // closed (because a modification like above is not persistent),
535 // which is why the checks below are expected to pass.
536 auto tx = d.vault.withdraw(
537 {.depositor = d.depositor,
538 .id = d.keylet.key,
539 .amount = STAmount(d.asset, Number(25, -1))});
540 env(tx, Ter{tecINSUFFICIENT_FUNDS});
541 env.close();
542 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900 - 25));
543 BEAST_EXPECT(
544 env.balance(d.depositor, d.assets) ==
545 STAmount(d.asset, start + Number(25, -1)));
546 BEAST_EXPECT(
547 env.balance(d.vaultAccount, d.assets) ==
548 STAmount(d.asset, Number(900 - 25, -1)));
549 BEAST_EXPECT(
550 env.balance(d.vaultAccount, d.shares) ==
551 STAmount(d.share, -Number(900 - 25, 0)));
552 }
553
554 {
555 testcase("Scale withdraw with rounding shares up (truncated post-fixCleanup3_4_0)");
556 // Pre-fixCleanup3_4_0:
557 // shares = round(875 * 3.75 / 87.5) = 38
558 // assets = 87.5 * 38 / 875 = 3.8 > 3.75 requested.
559 // Post-fixCleanup3_4_0:
560 // shares = floor(37.5) = 37
561 // assets = 87.5 * 37 / 875 = 3.7 <= 3.75 requested.
562
563 auto const start = env.balance(d.depositor, d.assets).number();
564 auto tx = d.vault.withdraw(
565 {.depositor = d.depositor,
566 .id = d.keylet.key,
567 .amount = STAmount(d.asset, Number(375, -2))});
568 env(tx);
569 env.close();
570 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(875 - 37));
571 BEAST_EXPECT(
572 env.balance(d.depositor, d.assets) ==
573 STAmount(d.asset, start + Number(37, -1)));
574 BEAST_EXPECT(
575 env.balance(d.vaultAccount, d.assets) ==
576 STAmount(d.asset, Number(875 - 37, -1)));
577 BEAST_EXPECT(
578 env.balance(d.vaultAccount, d.shares) ==
579 STAmount(d.share, -Number(875 - 37, 0)));
580 }
581
582 {
583 testcase("Scale withdraw with rounding shares down");
584 // Chained state: 838 shares outstanding, 83.8 assets.
585 // shares = floor(838 * 3.72 / 83.8) = floor(37.199...) = 37
586 // assets = 83.8 * 37 / 838 = 3.7 <= 3.72 requested.
587
588 auto const start = env.balance(d.depositor, d.assets).number();
589 auto tx = d.vault.withdraw(
590 {.depositor = d.depositor,
591 .id = d.keylet.key,
592 .amount = STAmount(d.asset, Number(372, -2))});
593 env(tx);
594 env.close();
595 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(838 - 37));
596 BEAST_EXPECT(
597 env.balance(d.depositor, d.assets) ==
598 STAmount(d.asset, start + Number(37, -1)));
599 BEAST_EXPECT(
600 env.balance(d.vaultAccount, d.assets) ==
601 STAmount(d.asset, Number(838 - 37, -1)));
602 BEAST_EXPECT(
603 env.balance(d.vaultAccount, d.shares) ==
604 STAmount(d.share, -Number(838 - 37, 0)));
605 }
606
607 {
608 testcase("Scale withdraw tiny amount rejected post-fixCleanup3_4_0");
609 // Chained state: 801 shares outstanding, 80.1 assets.
610 // shares = floor(801 * 0.09 / 80.1) = floor(0.9) = 0
611 // Zero shares => tecPRECISION_LOSS. State is unchanged.
612
613 auto const start = env.balance(d.depositor, d.assets).number();
614 auto tx = d.vault.withdraw(
615 {.depositor = d.depositor,
616 .id = d.keylet.key,
617 .amount = STAmount(d.asset, Number(9, -2))});
618 env(tx, Ter{tecPRECISION_LOSS});
619 env.close();
620 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(801));
621 BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
622 BEAST_EXPECT(
623 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(801, -1)));
624 BEAST_EXPECT(
625 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(801, 0)));
626 }
627
628 {
629 testcase("Scale withdraw rest");
630 auto const rest = env.balance(d.vaultAccount, d.assets).number();
631
632 tx = d.vault.withdraw(
633 {.depositor = d.depositor,
634 .id = d.keylet.key,
635 .amount = STAmount(d.asset, rest)});
636 env(tx);
637 env.close();
638 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
639 BEAST_EXPECT(env.balance(d.vaultAccount, d.assets).number() == 0);
640 BEAST_EXPECT(env.balance(d.vaultAccount, d.shares).number() == 0);
641 }
642 });
643
644 testCase(18, [&, this](Env& env, Data d) {
645 testcase("Scale clawback overflow");
646
647 {
648 auto tx = d.vault.deposit(
649 {.depositor = d.depositor, .id = d.keylet.key, .amount = d.asset(5)});
650 env(tx);
651 env.close();
652 }
653
654 {
655 auto tx = d.vault.clawback(
656 {.issuer = d.issuer,
657 .id = d.keylet.key,
658 .holder = d.depositor,
659 .amount = STAmount(d.asset, Number(10, 0))});
660 env(tx, Ter{tecPATH_DRY});
661 env.close();
662 }
663 });
664
665 testCase(1, [&, this](Env& env, Data d) {
666 // initial setup: deposit 100 IOU, receive 1000 shares
667 auto const start = env.balance(d.depositor, d.assets).number();
668 auto tx = d.vault.deposit(
669 {.depositor = d.depositor,
670 .id = d.keylet.key,
671 .amount = STAmount(d.asset, Number(100, 0))});
672 env(tx);
673 env.close();
674 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
675 BEAST_EXPECT(
676 env.balance(d.depositor, d.assets) == STAmount(d.asset, start - Number(100, 0)));
677 BEAST_EXPECT(
678 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(100, 0)));
679 BEAST_EXPECT(
680 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(1000, 0)));
681 {
682 testcase("Scale clawback exact");
683 // assetsToSharesWithdraw:
684 // shares = sharesTotal * (assets / assetsTotal)
685 // shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
686 // sharesToAssetsWithdraw:
687 // assets = assetsTotal * (shares / sharesTotal)
688 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
689
690 auto const start = env.balance(d.depositor, d.assets).number();
691 auto tx = d.vault.clawback(
692 {.issuer = d.issuer,
693 .id = d.keylet.key,
694 .holder = d.depositor,
695 .amount = STAmount(d.asset, Number(10, 0))});
696 env(tx);
697 env.close();
698 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900));
699 BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
700 BEAST_EXPECT(
701 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(90, 0)));
702 BEAST_EXPECT(
703 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(900, 0)));
704 }
705
706 {
707 testcase("Scale clawback insignificant amount");
708 auto tx = d.vault.clawback(
709 {.issuer = d.issuer,
710 .id = d.keylet.key,
711 .holder = d.depositor,
712 .amount = STAmount(d.asset, Number(4, -2))});
713 env(tx, Ter{tecPRECISION_LOSS});
714 }
715
716 {
717 testcase("Scale clawback with rounding assets");
718 // assetsToSharesWithdraw:
719 // shares = sharesTotal * (assets / assetsTotal)
720 // shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
721 // sharesToAssetsWithdraw:
722 // assets = assetsTotal * (shares / sharesTotal)
723 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
724
725 auto const start = env.balance(d.depositor, d.assets).number();
726 auto tx = d.vault.clawback(
727 {.issuer = d.issuer,
728 .id = d.keylet.key,
729 .holder = d.depositor,
730 .amount = STAmount(d.asset, Number(25, -1))});
731 env(tx);
732 env.close();
733 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(900 - 25));
734 BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
735 BEAST_EXPECT(
736 env.balance(d.vaultAccount, d.assets) ==
737 STAmount(d.asset, Number(900 - 25, -1)));
738 BEAST_EXPECT(
739 env.balance(d.vaultAccount, d.shares) ==
740 STAmount(d.share, -Number(900 - 25, 0)));
741 }
742
743 {
744 testcase("Scale clawback with rounding shares up (truncated post-fixCleanup3_4_0)");
745 // Pre-fixCleanup3_4_0:
746 // shares = round(875 * 3.75 / 87.5) = 38
747 // assets = 87.5 * 38 / 875 = 3.8 > 3.75 requested.
748 // Post-fixCleanup3_4_0:
749 // shares = floor(37.5) = 37
750 // assets = 87.5 * 37 / 875 = 3.7 <= 3.75 requested.
751
752 auto const start = env.balance(d.depositor, d.assets).number();
753 auto tx = d.vault.clawback(
754 {.issuer = d.issuer,
755 .id = d.keylet.key,
756 .holder = d.depositor,
757 .amount = STAmount(d.asset, Number(375, -2))});
758 env(tx);
759 env.close();
760 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(875 - 37));
761 BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
762 BEAST_EXPECT(
763 env.balance(d.vaultAccount, d.assets) ==
764 STAmount(d.asset, Number(875 - 37, -1)));
765 BEAST_EXPECT(
766 env.balance(d.vaultAccount, d.shares) ==
767 STAmount(d.share, -Number(875 - 37, 0)));
768 }
769
770 {
771 testcase("Scale clawback with rounding shares down");
772 // Chained state: 838 shares outstanding, 83.8 assets.
773 // shares = floor(838 * 3.72 / 83.8) = floor(37.199...) = 37
774 // assets = 83.8 * 37 / 838 = 3.7 <= 3.72 requested.
775
776 auto const start = env.balance(d.depositor, d.assets).number();
777 auto tx = d.vault.clawback(
778 {.issuer = d.issuer,
779 .id = d.keylet.key,
780 .holder = d.depositor,
781 .amount = STAmount(d.asset, Number(372, -2))});
782 env(tx);
783 env.close();
784 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(838 - 37));
785 BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
786 BEAST_EXPECT(
787 env.balance(d.vaultAccount, d.assets) ==
788 STAmount(d.asset, Number(838 - 37, -1)));
789 BEAST_EXPECT(
790 env.balance(d.vaultAccount, d.shares) ==
791 STAmount(d.share, -Number(838 - 37, 0)));
792 }
793
794 {
795 testcase("Scale clawback tiny amount rejected post-fixCleanup3_4_0");
796 // Chained state: 801 shares outstanding, 80.1 assets.
797 // shares = floor(801 * 0.09 / 80.1) = floor(0.9) = 0
798 // Zero shares => tecPRECISION_LOSS. State is unchanged.
799
800 auto const start = env.balance(d.depositor, d.assets).number();
801 auto tx = d.vault.clawback(
802 {.issuer = d.issuer,
803 .id = d.keylet.key,
804 .holder = d.depositor,
805 .amount = STAmount(d.asset, Number(9, -2))});
806 env(tx, Ter{tecPRECISION_LOSS});
807 env.close();
808 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(801));
809 BEAST_EXPECT(env.balance(d.depositor, d.assets) == STAmount(d.asset, start));
810 BEAST_EXPECT(
811 env.balance(d.vaultAccount, d.assets) == STAmount(d.asset, Number(801, -1)));
812 BEAST_EXPECT(
813 env.balance(d.vaultAccount, d.shares) == STAmount(d.share, -Number(801, 0)));
814 }
815
816 {
817 testcase("Scale clawback rest");
818 auto const rest = env.balance(d.vaultAccount, d.assets).number();
819 d.peek([](SLE& vault, auto&) -> bool {
820 vault[sfAssetsAvailable] = Number(5);
821 return true;
822 });
823
824 // Note, this transaction yields two different results:
825 // * in the open ledger, with AssetsAvailable = 5
826 // * when the ledger is closed with unmodified AssetsAvailable
827 // because a modification like above is not persistent.
828 tx = d.vault.clawback(
829 {.issuer = d.issuer,
830 .id = d.keylet.key,
831 .holder = d.depositor,
832 .amount = STAmount(d.asset, rest)});
833 env(tx);
834 env.close();
835 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
836 BEAST_EXPECT(env.balance(d.vaultAccount, d.assets).number() == 0);
837 BEAST_EXPECT(env.balance(d.vaultAccount, d.shares).number() == 0);
838 }
839 });
840
841 // Non-1:1 ratio (scale=1, 10:1 shares:assets) with an outstanding loan.
842 // Deposit 100 IOU → 1000 shares. Borrow 40 → assetsAvailable=60.
843 // Clawback 80 IOU → clamped to 60, then share math uses truncation.
844 testCase(1, [&, this](Env& env, Data d) {
845 using namespace loan_broker;
846 using namespace loan;
847
848 testcase("Scale clawback clamped with outstanding loan");
849
850 auto tx = d.vault.deposit(
851 {.depositor = d.depositor,
852 .id = d.keylet.key,
853 .amount = STAmount(d.asset, Number(100, 0))});
854 env(tx);
855 env.close();
856 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
857
858 // Create a loan broker backed by this vault
859 auto const brokerKeylet =
860 keylet::loanBroker(d.owner.id(), SeqProxy::rawSequence(env.seq(d.owner)));
861 env(set(d.owner, d.keylet.key));
862 env.close();
863
864 // Borrow 40: assetsAvailable=60, assetsTotal=100
865 env(set(d.depositor, brokerKeylet.key, STAmount(d.asset, Number(40, 0))),
866 loan::kInterestRate(TenthBips32(0)),
867 kGracePeriod(60),
868 kPaymentInterval(120),
869 kPaymentTotal(10),
870 Sig(sfCounterpartySignature, d.owner),
871 Fee(env.current()->fees().base * 2),
872 Ter(tesSUCCESS));
873 env.close();
874
875 {
876 auto const sle = env.le(d.keylet);
877 BEAST_EXPECT(sle->at(sfAssetsAvailable) == STAmount(d.asset, Number(60, 0)));
878 BEAST_EXPECT(sle->at(sfAssetsTotal) == STAmount(d.asset, Number(100, 0)));
879 }
880
881 // Request 80 IOU clawback — clamped to assetsAvailable (60)
882 // With scale=1 (10:1), 60 assets = 600 shares destroyed
883 tx = d.vault.clawback(
884 {.issuer = d.issuer,
885 .id = d.keylet.key,
886 .holder = d.depositor,
887 .amount = STAmount(d.asset, Number(80, 0))});
888 env(tx, Ter(tesSUCCESS));
889 env.close();
890
891 {
892 auto const sle = env.le(d.keylet);
893 BEAST_EXPECT(sle != nullptr);
894 BEAST_EXPECT(sle->at(sfAssetsAvailable) == STAmount(d.asset, Number(0, 0)));
895 BEAST_EXPECT(sle->at(sfAssetsTotal) == STAmount(d.asset, Number(40, 0)));
896
897 // 600 of 1000 shares destroyed, 400 remain
898 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(400));
899 }
900 });
901
902 // peek() writes the open ledger only; do not close() before le().
903 auto seedLargeTotal = [](Env& env,
904 Data& d,
905 Number const& total,
906 Number const& available,
907 std::uint64_t outstanding) {
908 auto tx = d.vault.deposit(
909 {.depositor = d.depositor,
910 .id = d.keylet.key,
911 .amount = STAmount(d.asset, Number(100, 0))});
912 env(tx);
913 env.close();
914 d.peek([&](SLE& vault, SLE& shares) -> bool {
915 vault[sfAssetsTotal] = total;
916 vault[sfAssetsAvailable] = available;
917 shares[sfOutstandingAmount] = outstanding;
918 return true;
919 });
920 };
921
922 auto expectVault = [this](
923 Env& env,
924 Data const& d,
925 Number const& total,
926 Number const& available,
927 STAmount const& shareBalance) {
928 auto const sle = env.le(d.keylet);
929 BEAST_EXPECT(sle != nullptr);
930 BEAST_EXPECT(sle->at(sfAssetsTotal) == total);
931 BEAST_EXPECT(sle->at(sfAssetsAvailable) == available);
932 BEAST_EXPECT(env.balance(d.depositor, d.shares) == shareBalance);
933 };
934
935 // T-6 is exact after the decade; recover 6.
936 testCase(0, [&, this](Env& env, Data d) {
937 testcase("Scale clawback uses posterior scale across decade boundary");
938
939 Number const midGridTotal{10000000000000005ll};
940 Number const available{6};
941 seedLargeTotal(env, d, midGridTotal, available, 10000000000000005ull);
942
943 auto tx =
944 d.vault.clawback({.issuer = d.issuer, .id = d.keylet.key, .holder = d.depositor});
945 env(tx, Ter(tesSUCCESS));
946 expectVault(env, d, midGridTotal - available, Number(0), d.share(94));
947 });
948
949 // T stays on the 10-asset grid; 6 is unrepresentable.
950 testCase(0, [&, this](Env& env, Data d) {
951 testcase("Scale clawback rejects amount below posterior scale");
952
953 Number const midGridTotal{12345678901234567ll};
954 Number const available{6};
955 seedLargeTotal(env, d, midGridTotal, available, 12345678901234567ull);
956
957 auto tx =
958 d.vault.clawback({.issuer = d.issuer, .id = d.keylet.key, .holder = d.depositor});
959 env(tx, Ter(tecPRECISION_LOSS));
960 expectVault(env, d, midGridTotal, available, d.share(100));
961 });
962
963 // A recovery larger than the anterior ULP also lands exactly on the finer posterior grid.
964 testCase(0, [&, this](Env& env, Data d) {
965 testcase("Scale clawback preserves exact posterior amount");
966
967 Number const midGridTotal{10000000000000005ll};
968 Number const available{15};
969 seedLargeTotal(env, d, midGridTotal, available, 10000000000000005ull);
970
971 auto tx =
972 d.vault.clawback({.issuer = d.issuer, .id = d.keylet.key, .holder = d.depositor});
973 env(tx, Ter(tesSUCCESS));
974 expectVault(env, d, midGridTotal - available, Number(0), d.share(85));
975 });
976
977 testCase(0, [&, this](Env& env, Data d) {
978 testcase("Scale deposit rejects amount below posterior scale");
979
980 Number const midGridTotal{10000000000000005ll};
981 Number const available{100};
982 seedLargeTotal(env, d, midGridTotal, available, 10000000000000005ull);
983
984 auto const assetsBefore = env.balance(d.depositor, d.assets);
985 auto tx = d.vault.deposit(
986 {.depositor = d.depositor,
987 .id = d.keylet.key,
988 .amount = STAmount(d.asset, Number(6))});
989 env(tx, Ter(tecPRECISION_LOSS));
990 expectVault(env, d, midGridTotal, available, d.share(100));
991 BEAST_EXPECT(env.balance(d.depositor, d.assets) == assetsBefore);
992 });
993
994 testCase(0, [&, this](Env& env, Data d) {
995 testcase("Scale withdraw uses posterior scale across decade boundary");
996
997 Number const midGridTotal{10000000000000005ll};
998 Number const available{100};
999 seedLargeTotal(env, d, midGridTotal, available, 10000000000000005ull);
1000
1001 auto const assetsBefore = env.balance(d.depositor, d.assets);
1002 auto tx = d.vault.withdraw(
1003 {.depositor = d.depositor,
1004 .id = d.keylet.key,
1005 .amount = STAmount(d.share, Number(15))});
1006 env(tx, Ter(tesSUCCESS));
1007 expectVault(env, d, midGridTotal - Number(15), Number(85), d.share(85));
1008 BEAST_EXPECT(
1009 env.balance(d.depositor, d.assets) ==
1010 STAmount(d.asset, assetsBefore.number() + Number(15)));
1011 });
1012 }
1013
1014 void
1016 {
1017 testcase("Assets Maximum");
1018
1019 using namespace test::jtx;
1020
1021 Env env{*this, testableAmendments()};
1022 Account const owner{"owner"};
1023 Account const issuer{"issuer"};
1024
1025 Vault const vault{env};
1026 env.fund(XRP(1'000'000), issuer, owner);
1027 env.close();
1028
1030 BEAST_EXPECT(maxInt64 == "9223372036854775807");
1031
1032 auto const maxInt64Plus1 = std::to_string(
1034 BEAST_EXPECT(maxInt64Plus1 == "9223372036854775808");
1035
1036 // Naming things is hard
1037 auto const maxInt64Plus2 = std::to_string(
1039 BEAST_EXPECT(maxInt64Plus2 == "9223372036854775809");
1040
1041 auto const initialXRP = to_string(kInitialXrp);
1042 BEAST_EXPECT(initialXRP == "100000000000000000");
1043
1044 auto const initialXRPPlus1 = to_string(kInitialXrp + 1);
1045 BEAST_EXPECT(initialXRPPlus1 == "100000000000000001");
1046
1047 {
1048 testcase("Assets Maximum: XRP");
1049
1050 PrettyAsset const xrpAsset = xrpIssue();
1051
1052 auto [tx, keylet] = vault.create({.owner = owner, .asset = xrpAsset});
1053 tx[sfData] = "4D65746144617461";
1054
1055 tx[sfAssetsMaximum] = maxInt64;
1056 env(tx, Ter(tefEXCEPTION));
1057 env.close();
1058
1059 tx[sfAssetsMaximum] = initialXRPPlus1;
1060 env(tx, Ter(tefEXCEPTION));
1061 env.close();
1062
1063 tx[sfAssetsMaximum] = initialXRP;
1064 env(tx);
1065 env.close();
1066
1067 // There are several parse failures expected in this function, so just disable it once.
1068 env.setParseFailureExpected(true);
1069 try
1070 {
1071 tx[sfAssetsMaximum] = maxInt64Plus1;
1072 env(tx, Ter(tefEXCEPTION));
1073 env.close();
1074 // should throw in parser
1075 fail();
1076 }
1077 catch (std::exception const& e)
1078 {
1079 BEAST_EXPECT(
1080 std::string(e.what()) ==
1081 "invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
1082 }
1083
1084 try
1085 {
1086 tx[sfAssetsMaximum] = maxInt64Plus2;
1087 env(tx, Ter(tefEXCEPTION));
1088 // should throw in parser
1089 fail();
1090 }
1091 catch (std::exception const& e)
1092 {
1093 BEAST_EXPECT(
1094 std::string(e.what()) ==
1095 "invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
1096 }
1097
1098 auto const newKeylet = keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
1099 try
1100 {
1101 auto const insertAt = maxInt64Plus2.size() - 3;
1102 auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
1103 maxInt64Plus2.substr(insertAt); // (max int64+2) / 1000
1104 BEAST_EXPECT(decimalTest == "9223372036854775.809");
1105 tx[sfAssetsMaximum] = decimalTest;
1106 env(tx);
1107 // should throw in parser
1108 fail();
1109 }
1110 catch (std::exception const& e)
1111 {
1112 BEAST_EXPECT(
1113 std::string(e.what()) ==
1114 "invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
1115 }
1116
1117 auto const vaultSle = env.le(newKeylet);
1118 BEAST_EXPECT(!vaultSle);
1119 }
1120
1121 {
1122 testcase("Assets Maximum: MPT");
1123
1124 PrettyAsset const mptAsset = [&]() {
1125 MPTTester mptt{env, issuer, kMptInitNoFund};
1126 mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
1127 env.close();
1128 PrettyAsset const mptAsset = mptt["MPT"];
1129 mptt.authorize({.account = owner});
1130 env.close();
1131 return mptAsset;
1132 }();
1133
1134 env(pay(issuer, owner, mptAsset(100'000)));
1135 env.close();
1136
1137 auto [tx, keylet] = vault.create({.owner = owner, .asset = mptAsset});
1138 tx[sfData] = "4D65746144617461";
1139
1140 tx[sfAssetsMaximum] = maxInt64;
1141 env(tx);
1142 env.close();
1143
1144 tx[sfAssetsMaximum] = initialXRPPlus1;
1145 env(tx);
1146 env.close();
1147
1148 tx[sfAssetsMaximum] = initialXRP;
1149 env(tx);
1150 env.close();
1151
1152 try
1153 {
1154 tx[sfAssetsMaximum] = maxInt64Plus2;
1155 env(tx, Ter(tefEXCEPTION));
1156 // should throw in parser
1157 fail();
1158 }
1159 catch (std::exception const& e)
1160 {
1161 BEAST_EXPECT(
1162 std::string(e.what()) ==
1163 "invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
1164 }
1165
1166 auto const newKeylet = keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
1167 try
1168 {
1169 auto const insertAt = maxInt64Plus2.size() - 1;
1170 auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
1171 maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
1172 BEAST_EXPECT(decimalTest == "922337203685477580.9");
1173 tx[sfAssetsMaximum] = decimalTest;
1174 env(tx);
1175 // should throw in parser
1176 fail();
1177 }
1178 catch (std::exception const& e)
1179 {
1180 BEAST_EXPECT(
1181 std::string(e.what()) ==
1182 "invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
1183 }
1184
1185 auto const vaultSle = env.le(newKeylet);
1186 BEAST_EXPECT(!vaultSle);
1187 }
1188
1189 {
1190 testcase("Assets Maximum: IOU");
1191
1192 // Almost anything goes with IOUs
1193 PrettyAsset const iouAsset = issuer["IOU"];
1194 env.trust(iouAsset(1000), owner);
1195 env(pay(issuer, owner, iouAsset(200)));
1196 env.close();
1197
1198 auto [tx, keylet] = vault.create({.owner = owner, .asset = iouAsset});
1199 tx[sfData] = "4D65746144617461";
1200
1201 tx[sfAssetsMaximum] = maxInt64;
1202 env(tx);
1203 env.close();
1204
1205 tx[sfAssetsMaximum] = initialXRPPlus1;
1206 env(tx);
1207 env.close();
1208
1209 tx[sfAssetsMaximum] = initialXRP;
1210 env(tx);
1211 env.close();
1212
1213 // Since several tests are expected to have parser failures, leave this flag set for the
1214 // remainder of this function.
1215 env.setParseFailureExpected(true);
1216 try
1217 {
1218 tx[sfAssetsMaximum] = maxInt64Plus2;
1219 env(tx);
1220 // should throw in parser
1221 fail();
1222 }
1223 catch (std::exception const& e)
1224 {
1225 BEAST_EXPECT(
1226 std::string(e.what()) ==
1227 "invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
1228 }
1229
1230 tx[sfAssetsMaximum] = "1000000000000000e80";
1231 env.close();
1232
1233 tx[sfAssetsMaximum] = "1000000000000000e-96";
1234 env.close();
1235
1236 // These values will be rounded to 15 significant digits
1237 {
1238 auto const newKeylet =
1239 keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
1240 try
1241 {
1242 auto const insertAt = maxInt64Plus2.size() - 1;
1243 auto const decimalTest = maxInt64Plus2.substr(0, insertAt) + "." +
1244 maxInt64Plus2.substr(insertAt); // (max int64+2) / 10
1245 BEAST_EXPECT(decimalTest == "922337203685477580.9");
1246 tx[sfAssetsMaximum] = decimalTest;
1247 env(tx);
1248 // should throw in parser
1249 fail();
1250 }
1251 catch (std::exception const& e)
1252 {
1253 BEAST_EXPECT(
1254 std::string(e.what()) ==
1255 "invalidParamsField 'tx_json.AssetsMaximum' has invalid data.");
1256 }
1257
1258 auto const vaultSle = env.le(newKeylet);
1259 BEAST_EXPECT(!vaultSle);
1260 }
1261 {
1262 tx[sfAssetsMaximum] = "9223372036854775807e40"; // max int64 * 10^40
1263 auto const newKeylet =
1264 keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
1265 env(tx);
1266 env.close();
1267
1268 auto const vaultSle = env.le(newKeylet);
1269 if (!BEAST_EXPECT(vaultSle))
1270 return;
1271
1272 BEAST_EXPECT(
1273 (vaultSle->at(sfAssetsMaximum) ==
1274 Number{9223372036854776, 43, Number::Normalized{}}));
1275 }
1276 {
1277 tx[sfAssetsMaximum] = "9223372036854775807e-40"; // max int64 * 10^-40
1278 auto const newKeylet =
1279 keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
1280 env(tx);
1281 env.close();
1282
1283 auto const vaultSle = env.le(newKeylet);
1284 if (!BEAST_EXPECT(vaultSle))
1285 return;
1286
1287 BEAST_EXPECT(
1288 (vaultSle->at(sfAssetsMaximum) ==
1289 Number{9223372036854776, -37, Number::Normalized{}}));
1290 }
1291 {
1292 tx[sfAssetsMaximum] = "9223372036854775807e-100"; // max int64 * 10^-100
1293 auto const newKeylet =
1294 keylet::vault(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
1295 env(tx);
1296 env.close();
1297
1298 // Field 'AssetsMaximum' may not be explicitly set to default.
1299 auto const vaultSle = env.le(newKeylet);
1300 if (!BEAST_EXPECT(vaultSle))
1301 return;
1302
1303 BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == kNumZero);
1304 }
1305
1306 // What _can't_ IOUs do?
1307 // 1. Exceed maximum exponent / offset
1308 tx[sfAssetsMaximum] = "1000000000000000e81";
1309 env(tx, Ter(tefEXCEPTION));
1310 env.close();
1311
1312 // 2. Mantissa larger than uint64 max
1313 try
1314 {
1315 auto const g = env.getParseFailureGuard(true);
1316 tx[sfAssetsMaximum] = "18446744073709551617e5"; // uint64 max + 1
1317 env(tx);
1318 BEAST_EXPECTS(false, "Expected parse_error for mantissa larger than uint64 max");
1319 }
1320 catch (ParseError const& e)
1321 {
1322 using namespace std::string_literals;
1323 BEAST_EXPECT(
1324 e.what() == "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."s);
1325 }
1326 }
1327 }
1328
1329public:
1330 void
1331 run() override
1332 {
1333 testScaleIOU();
1335 }
1336};
1337
1339
1340} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:554
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
constexpr TIss const & get() const
A currency issued by an account.
Definition Issue.h:18
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:59
Discardable, editable view to a ledger.
Definition Sandbox.h:18
void apply(RawView &to)
Definition Sandbox.h:38
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
void run() override
Runs the suite.
Shared base for the Vault*_test family under src/test/app/vault/.
test::jtx::PrettyAsset PrettyAsset
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
void update(SLE::Ref sle) override
Indicate changes to a peeked SLE.
T max(T... args)
Keylet computation functions.
Definition Indexes.h:40
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:591
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:597
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static constexpr Number kNumZero
Definition Number.h:663
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
Definition STAmount.h:794
@ tefEXCEPTION
Definition TER.h:167
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:454
BEAST_DEFINE_TESTSUITE_PRIO(AccountSet, app, xrpl, 1)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
STLedgerEntry SLE
@ TapNone
Definition ApplyView.h:28
@ tecPATH_DRY
Definition TER.h:302
@ tecINSUFFICIENT_FUNDS
Definition TER.h:333
@ tecPRECISION_LOSS
Definition TER.h:371
constexpr XRPAmount kInitialXrp
Configure the native currency.
@ tesSUCCESS
Definition TER.h:250
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
T to_string(T... args)
T what(T... args)