xrpld
Loading...
Searching...
No Matches
STAmount_test.cpp
1
2#include <xrpl/basics/Number.h>
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/basics/random.h>
5#include <xrpl/beast/hash/uhash.h>
6#include <xrpl/beast/unit_test/suite.h>
7#include <xrpl/beast/utility/Zero.h>
8#include <xrpl/json/json_forwards.h>
9#include <xrpl/json/json_value.h>
10#include <xrpl/protocol/AccountID.h>
11#include <xrpl/protocol/Feature.h>
12#include <xrpl/protocol/IOUAmount.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/Issue.h>
15#include <xrpl/protocol/MPTAmount.h>
16#include <xrpl/protocol/MPTIssue.h>
17#include <xrpl/protocol/Rate.h>
18#include <xrpl/protocol/Rules.h>
19#include <xrpl/protocol/SField.h>
20#include <xrpl/protocol/STAmount.h>
21#include <xrpl/protocol/Serializer.h>
22#include <xrpl/protocol/UintTypes.h>
23#include <xrpl/protocol/XRPAmount.h>
24
25#include <cstdint>
26#include <exception>
27#include <limits>
28#include <stdexcept>
29#include <string>
30#include <type_traits>
31#include <typeinfo>
32#include <unordered_set>
33
34namespace xrpl {
35
37{
38public:
39 static STAmount
41 {
42 Serializer ser;
43 s.add(ser);
44
45 SerialIter sit(ser.slice());
46 return STAmount(sit, sfGeneric);
47 }
48
49 //--------------------------------------------------------------------------
50 static STAmount
51 roundSelf(STAmount const& amount)
52 {
53 if (amount.native())
54 return amount;
55
56 std::uint64_t mantissa = amount.mantissa();
57 std::uint64_t const valueDigits = mantissa % 1000000000;
58
59 if (valueDigits == 1)
60 {
61 mantissa--;
62
63 if (mantissa < STAmount::kMinValue)
64 return {amount.asset(), mantissa, amount.exponent(), amount.negative()};
65
66 return {
67 amount.asset(),
68 mantissa,
69 amount.exponent(),
70 amount.negative(),
72 }
73
74 if (valueDigits == 999999999)
75 {
76 mantissa++;
77
78 if (mantissa > STAmount::kMaxValue)
79 return {amount.asset(), mantissa, amount.exponent(), amount.negative()};
80
81 return {
82 amount.asset(),
83 mantissa,
84 amount.exponent(),
85 amount.negative(),
87 }
88
89 return amount;
90 }
91
92 void
93 roundTest(int n, int d, int m)
94 {
95 // check STAmount rounding
96 STAmount const num(noIssue(), n);
97 STAmount const den(noIssue(), d);
98 STAmount const mul(noIssue(), m);
99 STAmount const quot = divide(STAmount(n), STAmount(d), noIssue());
100 STAmount const res = roundSelf(multiply(quot, mul, noIssue()));
101
102 BEAST_EXPECT(!res.native());
103
104 STAmount const cmp(noIssue(), (n * m) / d);
105
106 BEAST_EXPECT(!cmp.native());
107
108 BEAST_EXPECT(cmp.get<Issue>().currency == res.get<Issue>().currency);
109
110 if (res != cmp)
111 {
112 log << "(" << num.getText() << "/" << den.getText() << ") X " << mul.getText() << " = "
113 << res.getText() << " not " << cmp.getText();
114 fail("Rounding");
115 return;
116 }
117 }
118
119 void
120 mulTest(int a, int b)
121 {
122 STAmount const aa(noIssue(), a);
123 STAmount const bb(noIssue(), b);
124 STAmount const prod1(multiply(aa, bb, noIssue()));
125
126 BEAST_EXPECT(!prod1.native());
127
128 STAmount const prod2(
129 noIssue(), static_cast<std::uint64_t>(a) * static_cast<std::uint64_t>(b));
130
131 if (prod1 != prod2)
132 {
133 log << "nn(" << aa.getFullText() << " * " << bb.getFullText()
134 << ") = " << prod1.getFullText() << " not " << prod2.getFullText();
135 fail("Multiplication result is not exact");
136 }
137 }
138
139 //--------------------------------------------------------------------------
140
141 void
142 testSetValue(std::string const& value, Issue const& issue, bool success = true)
143 {
144 try
145 {
146 STAmount const amount = amountFromString(issue, value);
147 BEAST_EXPECT(amount.getText() == value);
148 }
149 catch (std::exception const&)
150 {
151 BEAST_EXPECT(!success);
152 }
153 }
154
155 void
157 {
158 {
159 testcase("set value (native)");
160
161 Issue const xrp(xrpIssue());
162
163 // fractional XRP (i.e. drops)
164 testSetValue("1", xrp);
165 testSetValue("22", xrp);
166 testSetValue("333", xrp);
167 testSetValue("4444", xrp);
168 testSetValue("55555", xrp);
169 testSetValue("666666", xrp);
170
171 // 1 XRP up to 100 billion, in powers of 10 (in drops)
172 testSetValue("1000000", xrp);
173 testSetValue("10000000", xrp);
174 testSetValue("100000000", xrp);
175 testSetValue("1000000000", xrp);
176 testSetValue("10000000000", xrp);
177 testSetValue("100000000000", xrp);
178 testSetValue("1000000000000", xrp);
179 testSetValue("10000000000000", xrp);
180 testSetValue("100000000000000", xrp);
181 testSetValue("1000000000000000", xrp);
182 testSetValue("10000000000000000", xrp);
183 testSetValue("100000000000000000", xrp);
184
185 // Invalid native values:
186 testSetValue("1.1", xrp, false);
187 testSetValue("100000000000000001", xrp, false);
188 testSetValue("1000000000000000000", xrp, false);
189 }
190
191 {
192 testcase("set value (iou)");
193
194 Issue const usd(Currency(0x5553440000000000), AccountID(0x4985601));
195
196 testSetValue("1", usd);
197 testSetValue("10", usd);
198 testSetValue("100", usd);
199 testSetValue("1000", usd);
200 testSetValue("10000", usd);
201 testSetValue("100000", usd);
202 testSetValue("1000000", usd);
203 testSetValue("10000000", usd);
204 testSetValue("100000000", usd);
205 testSetValue("1000000000", usd);
206 testSetValue("10000000000", usd);
207
208 testSetValue("1234567.1", usd);
209 testSetValue("1234567.12", usd);
210 testSetValue("1234567.123", usd);
211 testSetValue("1234567.1234", usd);
212 testSetValue("1234567.12345", usd);
213 testSetValue("1234567.123456", usd);
214 testSetValue("1234567.1234567", usd);
215 testSetValue("1234567.12345678", usd);
216 testSetValue("1234567.123456789", usd);
217 }
218 }
219
220 //--------------------------------------------------------------------------
221
222 void
224 {
225 testcase("native currency");
226
227 STAmount const zeroSt;
228 STAmount const one(1);
229 STAmount const hundred(100);
230
231 // VFALCO NOTE Why repeat "STAmount fail" so many times??
232 unexpected(serializeAndDeserialize(zeroSt) != zeroSt, "STAmount fail");
233 unexpected(serializeAndDeserialize(one) != one, "STAmount fail");
234 unexpected(serializeAndDeserialize(hundred) != hundred, "STAmount fail");
235 unexpected(!zeroSt.native(), "STAmount fail");
236 unexpected(!hundred.native(), "STAmount fail");
237 unexpected(zeroSt != beast::kZero, "STAmount fail");
238 unexpected(one == beast::kZero, "STAmount fail");
239 unexpected(hundred == beast::kZero, "STAmount fail");
240 unexpected((zeroSt < zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
241 unexpected(!(zeroSt < one), "STAmount fail");
242 unexpected(!(zeroSt < hundred), "STAmount fail");
243 unexpected((one < zeroSt), "STAmount fail");
244 unexpected((one < one), "STAmount fail"); // NOLINT(misc-redundant-expression)
245 unexpected(!(one < hundred), "STAmount fail");
246 unexpected((hundred < zeroSt), "STAmount fail");
247 unexpected((hundred < one), "STAmount fail");
248 unexpected((hundred < hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
249 unexpected((zeroSt > zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
250 unexpected((zeroSt > one), "STAmount fail");
251 unexpected((zeroSt > hundred), "STAmount fail");
252 unexpected(!(one > zeroSt), "STAmount fail");
253 unexpected((one > one), "STAmount fail"); // NOLINT(misc-redundant-expression)
254 unexpected((one > hundred), "STAmount fail");
255 unexpected(!(hundred > zeroSt), "STAmount fail");
256 unexpected(!(hundred > one), "STAmount fail");
257 unexpected((hundred > hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
258 unexpected(!(zeroSt <= zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
259 unexpected(!(zeroSt <= one), "STAmount fail");
260 unexpected(!(zeroSt <= hundred), "STAmount fail");
261 unexpected((one <= zeroSt), "STAmount fail");
262 unexpected(!(one <= one), "STAmount fail"); // NOLINT(misc-redundant-expression)
263 unexpected(!(one <= hundred), "STAmount fail");
264 unexpected((hundred <= zeroSt), "STAmount fail");
265 unexpected((hundred <= one), "STAmount fail");
266 unexpected(!(hundred <= hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
267 unexpected(!(zeroSt >= zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
268 unexpected((zeroSt >= one), "STAmount fail");
269 unexpected((zeroSt >= hundred), "STAmount fail");
270 unexpected(!(one >= zeroSt), "STAmount fail");
271 unexpected(!(one >= one), "STAmount fail"); // NOLINT(misc-redundant-expression)
272 unexpected((one >= hundred), "STAmount fail");
273 unexpected(!(hundred >= zeroSt), "STAmount fail");
274 unexpected(!(hundred >= one), "STAmount fail");
275 unexpected(!(hundred >= hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
276 unexpected(!(zeroSt == zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
277 unexpected((zeroSt == one), "STAmount fail");
278 unexpected((zeroSt == hundred), "STAmount fail");
279 unexpected((one == zeroSt), "STAmount fail");
280 unexpected(!(one == one), "STAmount fail"); // NOLINT(misc-redundant-expression)
281 unexpected((one == hundred), "STAmount fail");
282 unexpected((hundred == zeroSt), "STAmount fail");
283 unexpected((hundred == one), "STAmount fail");
284 unexpected(!(hundred == hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
285 unexpected((zeroSt != zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
286 unexpected(!(zeroSt != one), "STAmount fail");
287 unexpected(!(zeroSt != hundred), "STAmount fail");
288 unexpected(!(one != zeroSt), "STAmount fail");
289 unexpected((one != one), "STAmount fail"); // NOLINT(misc-redundant-expression)
290 unexpected(!(one != hundred), "STAmount fail");
291 unexpected(!(hundred != zeroSt), "STAmount fail");
292 unexpected(!(hundred != one), "STAmount fail");
293 unexpected((hundred != hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
294 unexpected(STAmount().getText() != "0", "STAmount fail");
295 unexpected(STAmount(31).getText() != "31", "STAmount fail");
296 unexpected(STAmount(310).getText() != "310", "STAmount fail");
297 unexpected(to_string(Currency()) != "XRP", "cHC(XRP)");
298 Currency c;
299 unexpected(!toCurrency(c, "USD"), "create USD currency");
300 unexpected(to_string(c) != "USD", "check USD currency");
301
302 std::string const cur = "015841551A748AD2C1F76FF6ECB0CCCD00000000";
303 unexpected(!toCurrency(c, cur), "create custom currency");
304 unexpected(to_string(c) != cur, "check custom currency");
305 }
306
307 //--------------------------------------------------------------------------
308
309 void
311 {
312 testcase("custom currency");
313
314 STAmount const zeroSt(noIssue());
315 STAmount const one(noIssue(), 1);
316 STAmount const hundred(noIssue(), 100);
317
318 unexpected(serializeAndDeserialize(zeroSt) != zeroSt, "STAmount fail");
319 unexpected(serializeAndDeserialize(one) != one, "STAmount fail");
320 unexpected(serializeAndDeserialize(hundred) != hundred, "STAmount fail");
321 unexpected(zeroSt.native(), "STAmount fail");
322 unexpected(hundred.native(), "STAmount fail");
323 unexpected(zeroSt != beast::kZero, "STAmount fail");
324 unexpected(one == beast::kZero, "STAmount fail");
325 unexpected(hundred == beast::kZero, "STAmount fail");
326 unexpected((zeroSt < zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
327 unexpected(!(zeroSt < one), "STAmount fail");
328 unexpected(!(zeroSt < hundred), "STAmount fail");
329 unexpected((one < zeroSt), "STAmount fail");
330 unexpected((one < one), "STAmount fail"); // NOLINT(misc-redundant-expression)
331 unexpected(!(one < hundred), "STAmount fail");
332 unexpected((hundred < zeroSt), "STAmount fail");
333 unexpected((hundred < one), "STAmount fail");
334 unexpected((hundred < hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
335 unexpected((zeroSt > zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
336 unexpected((zeroSt > one), "STAmount fail");
337 unexpected((zeroSt > hundred), "STAmount fail");
338 unexpected(!(one > zeroSt), "STAmount fail");
339 unexpected((one > one), "STAmount fail"); // NOLINT(misc-redundant-expression)
340 unexpected((one > hundred), "STAmount fail");
341 unexpected(!(hundred > zeroSt), "STAmount fail");
342 unexpected(!(hundred > one), "STAmount fail");
343 unexpected((hundred > hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
344 unexpected(!(zeroSt <= zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
345 unexpected(!(zeroSt <= one), "STAmount fail");
346 unexpected(!(zeroSt <= hundred), "STAmount fail");
347 unexpected((one <= zeroSt), "STAmount fail");
348 unexpected(!(one <= one), "STAmount fail"); // NOLINT(misc-redundant-expression)
349 unexpected(!(one <= hundred), "STAmount fail");
350 unexpected((hundred <= zeroSt), "STAmount fail");
351 unexpected((hundred <= one), "STAmount fail");
352 unexpected(!(hundred <= hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
353 unexpected(!(zeroSt >= zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
354 unexpected((zeroSt >= one), "STAmount fail");
355 unexpected((zeroSt >= hundred), "STAmount fail");
356 unexpected(!(one >= zeroSt), "STAmount fail");
357 unexpected(!(one >= one), "STAmount fail"); // NOLINT(misc-redundant-expression)
358 unexpected((one >= hundred), "STAmount fail");
359 unexpected(!(hundred >= zeroSt), "STAmount fail");
360 unexpected(!(hundred >= one), "STAmount fail");
361 unexpected(!(hundred >= hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
362 unexpected(!(zeroSt == zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
363 unexpected((zeroSt == one), "STAmount fail");
364 unexpected((zeroSt == hundred), "STAmount fail");
365 unexpected((one == zeroSt), "STAmount fail");
366 unexpected(!(one == one), "STAmount fail"); // NOLINT(misc-redundant-expression)
367 unexpected((one == hundred), "STAmount fail");
368 unexpected((hundred == zeroSt), "STAmount fail");
369 unexpected((hundred == one), "STAmount fail");
370 unexpected(!(hundred == hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
371 unexpected((zeroSt != zeroSt), "STAmount fail"); // NOLINT(misc-redundant-expression)
372 unexpected(!(zeroSt != one), "STAmount fail");
373 unexpected(!(zeroSt != hundred), "STAmount fail");
374 unexpected(!(one != zeroSt), "STAmount fail");
375 unexpected((one != one), "STAmount fail"); // NOLINT(misc-redundant-expression)
376 unexpected(!(one != hundred), "STAmount fail");
377 unexpected(!(hundred != zeroSt), "STAmount fail");
378 unexpected(!(hundred != one), "STAmount fail");
379 unexpected((hundred != hundred), "STAmount fail"); // NOLINT(misc-redundant-expression)
380 unexpected(STAmount(noIssue()).getText() != "0", "STAmount fail");
381 unexpected(STAmount(noIssue(), 31).getText() != "31", "STAmount fail");
382 unexpected(STAmount(noIssue(), 31, 1).getText() != "310", "STAmount fail");
383 unexpected(STAmount(noIssue(), 31, -1).getText() != "3.1", "STAmount fail");
384 unexpected(STAmount(noIssue(), 31, -2).getText() != "0.31", "STAmount fail");
386 multiply(STAmount(noIssue(), 20), STAmount(3), noIssue()).getText() != "60",
387 "STAmount multiply fail 1");
389 multiply(STAmount(noIssue(), 20), STAmount(3), xrpIssue()).getText() != "60",
390 "STAmount multiply fail 2");
392 multiply(STAmount(20), STAmount(3), noIssue()).getText() != "60",
393 "STAmount multiply fail 3");
395 multiply(STAmount(20), STAmount(3), xrpIssue()).getText() != "60",
396 "STAmount multiply fail 4");
397
398 if (divide(STAmount(noIssue(), 60), STAmount(3), noIssue()).getText() != "20")
399 {
400 log << "60/3 = " << divide(STAmount(noIssue(), 60), STAmount(3), noIssue()).getText();
401 fail("STAmount divide fail");
402 }
403 else
404 {
405 pass();
406 }
407
409 divide(STAmount(noIssue(), 60), STAmount(3), xrpIssue()).getText() != "20",
410 "STAmount divide fail");
411
413 divide(STAmount(noIssue(), 60), STAmount(noIssue(), 3), noIssue()).getText() != "20",
414 "STAmount divide fail");
415
417 divide(STAmount(noIssue(), 60), STAmount(noIssue(), 3), xrpIssue()).getText() != "20",
418 "STAmount divide fail");
419
420 STAmount const a1(noIssue(), 60);
421 STAmount const a2(noIssue(), 10, -1);
422
424 divide(a2, a1, noIssue()) != amountFromQuality(getRate(a1, a2)),
425 "STAmount setRate(getRate) fail");
426
428 divide(a1, a2, noIssue()) != amountFromQuality(getRate(a2, a1)),
429 "STAmount setRate(getRate) fail");
430 }
431
432 //--------------------------------------------------------------------------
433
434 void
436 {
437 testcase("arithmetic");
438
439 // Test currency multiplication and division operations such as
440 // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed,
441 // and getNeeded
442
444 getRate(STAmount(1), STAmount(10)) !=
445 (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
446 "STAmount getRate fail 1");
447
449 getRate(STAmount(10), STAmount(1)) !=
450 (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
451 "STAmount getRate fail 2");
452
454 getRate(STAmount(noIssue(), 1), STAmount(noIssue(), 10)) !=
455 (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
456 "STAmount getRate fail 3");
457
459 getRate(STAmount(noIssue(), 10), STAmount(noIssue(), 1)) !=
460 (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
461 "STAmount getRate fail 4");
462
464 getRate(STAmount(noIssue(), 1), STAmount(10)) !=
465 (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
466 "STAmount getRate fail 5");
467
469 getRate(STAmount(noIssue(), 10), STAmount(1)) !=
470 (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
471 "STAmount getRate fail 6");
472
474 getRate(STAmount(1), STAmount(noIssue(), 10)) !=
475 (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
476 "STAmount getRate fail 7");
477
479 getRate(STAmount(10), STAmount(noIssue(), 1)) !=
480 (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
481 "STAmount getRate fail 8");
482
483 roundTest(1, 3, 3);
484 roundTest(2, 3, 9);
485 roundTest(1, 7, 21);
486 roundTest(1, 2, 4);
487 roundTest(3, 9, 18);
488 roundTest(7, 11, 44);
489
490 for (int i = 0; i <= 100000; ++i)
491 {
492 mulTest(randInt(10000000), randInt(10000000));
493 }
494 }
495
496 //--------------------------------------------------------------------------
497
498 void
500 {
501 testcase("underflow");
502
503 STAmount const bigNative(STAmount::kMaxNative / 2);
504 STAmount const bigValue(
506 STAmount const smallValue(
508 STAmount const zeroSt(noIssue(), 0);
509
510 STAmount const smallXSmall = multiply(smallValue, smallValue, noIssue());
511
512 BEAST_EXPECT(smallXSmall == beast::kZero);
513
514 STAmount bigDsmall = divide(smallValue, bigValue, noIssue());
515
516 BEAST_EXPECT(bigDsmall == beast::kZero);
517
518 BEAST_EXPECT(bigDsmall == beast::kZero);
519
520 bigDsmall = divide(smallValue, bigValue, xrpIssue());
521
522 BEAST_EXPECT(bigDsmall == beast::kZero);
523
524 bigDsmall = divide(smallValue, bigNative, xrpIssue());
525
526 BEAST_EXPECT(bigDsmall == beast::kZero);
527
528 // very bad offer
529 std::uint64_t r = getRate(smallValue, bigValue);
530
531 BEAST_EXPECT(r == 0);
532
533 // very good offer
534 r = getRate(bigValue, smallValue);
535
536 BEAST_EXPECT(r == 0);
537 }
538
539 //--------------------------------------------------------------------------
540
541 void
543 {
544 // VFALCO TODO There are no actual tests here, just printed output?
545 // Change this to actually do something.
546 }
547
548 void
550 {
552
553 {
554 STAmount const stnum{sfNumber};
555 BEAST_EXPECT(stnum.getSType() == STI_AMOUNT);
556 BEAST_EXPECT(stnum.getText() == "0");
557 BEAST_EXPECT(stnum.isDefault() == true);
558 BEAST_EXPECT(stnum.value() == Number{0});
559 }
560
561 {
562 BEAST_EXPECT(amountFromJson(sfNumber, json::Value(42)) == XRPAmount(42));
563 BEAST_EXPECT(amountFromJson(sfNumber, json::Value(-42)) == XRPAmount(-42));
564
565 BEAST_EXPECT(amountFromJson(sfNumber, json::UInt(42)) == XRPAmount(42));
566
567 BEAST_EXPECT(amountFromJson(sfNumber, "-123") == XRPAmount(-123));
568
569 BEAST_EXPECT(amountFromJson(sfNumber, "123") == XRPAmount(123));
570 BEAST_EXPECT(amountFromJson(sfNumber, "-123") == XRPAmount(-123));
571
572 BEAST_EXPECT(amountFromJson(sfNumber, "3.14e2") == XRPAmount(314));
573 BEAST_EXPECT(amountFromJson(sfNumber, "-3.14e2") == XRPAmount(-314));
574
575 BEAST_EXPECT(amountFromJson(sfNumber, "0") == XRPAmount(0));
576 BEAST_EXPECT(amountFromJson(sfNumber, "-0") == XRPAmount(0));
577
578 constexpr auto kIMin = std::numeric_limits<int>::min();
579 BEAST_EXPECT(amountFromJson(sfNumber, kIMin) == XRPAmount(kIMin));
580 BEAST_EXPECT(amountFromJson(sfNumber, std::to_string(kIMin)) == XRPAmount(kIMin));
581
582 constexpr auto kIMax = std::numeric_limits<int>::max();
583 BEAST_EXPECT(amountFromJson(sfNumber, kIMax) == XRPAmount(kIMax));
584 BEAST_EXPECT(amountFromJson(sfNumber, std::to_string(kIMax)) == XRPAmount(kIMax));
585
586 constexpr auto kUMax = std::numeric_limits<unsigned int>::max();
587 BEAST_EXPECT(amountFromJson(sfNumber, kUMax) == XRPAmount(kUMax));
588 BEAST_EXPECT(amountFromJson(sfNumber, std::to_string(kUMax)) == XRPAmount(kUMax));
589
590 // XRP does not handle fractional part
591 try
592 {
593 auto _ = amountFromJson(sfNumber, "0.0");
594 BEAST_EXPECT(false);
595 }
596 catch (std::runtime_error const& e)
597 {
598 std::string const expected = "XRP and MPT must be specified as integral amount.";
599 BEAST_EXPECT(e.what() == expected);
600 }
601
602 // XRP does not handle fractional part
603 try
604 {
605 auto _ = amountFromJson(sfNumber, "1000e-2");
606 BEAST_EXPECT(false);
607 }
608 catch (std::runtime_error const& e)
609 {
610 std::string const expected = "XRP and MPT must be specified as integral amount.";
611 BEAST_EXPECT(e.what() == expected);
612 }
613
614 // Obvious non-numbers tested here
615 try
616 {
617 auto _ = amountFromJson(sfNumber, "");
618 BEAST_EXPECT(false);
619 }
620 catch (std::runtime_error const& e)
621 {
622 std::string const expected = "'' is not a number";
623 BEAST_EXPECT(e.what() == expected);
624 }
625
626 try
627 {
628 auto _ = amountFromJson(sfNumber, "e");
629 BEAST_EXPECT(false);
630 }
631 catch (std::runtime_error const& e)
632 {
633 std::string const expected = "'e' is not a number";
634 BEAST_EXPECT(e.what() == expected);
635 }
636
637 try
638 {
639 auto _ = amountFromJson(sfNumber, "1e");
640 BEAST_EXPECT(false);
641 }
642 catch (std::runtime_error const& e)
643 {
644 std::string const expected = "'1e' is not a number";
645 BEAST_EXPECT(e.what() == expected);
646 }
647
648 try
649 {
650 auto _ = amountFromJson(sfNumber, "e2");
651 BEAST_EXPECT(false);
652 }
653 catch (std::runtime_error const& e)
654 {
655 std::string const expected = "'e2' is not a number";
656 BEAST_EXPECT(e.what() == expected);
657 }
658
659 try
660 {
661 auto _ = amountFromJson(sfNumber, json::Value());
662 BEAST_EXPECT(false);
663 }
664 catch (std::runtime_error const& e)
665 {
666 std::string const expected = "XRP may not be specified with a null Json value";
667 BEAST_EXPECT(e.what() == expected);
668 }
669
670 try
671 {
672 auto _ = amountFromJson(
673 sfNumber,
674 "123456789012345678901234567890123456789012345678901234"
675 "5678"
676 "901234567890123456789012345678901234567890123456789012"
677 "3456"
678 "78901234567890123456789012345678901234567890");
679 BEAST_EXPECT(false);
680 }
681 catch (std::bad_cast const& e)
682 {
683 BEAST_EXPECT(true);
684 }
685
686 // We do not handle leading zeros
687 try
688 {
689 auto _ = amountFromJson(sfNumber, "001");
690 BEAST_EXPECT(false);
691 }
692 catch (std::runtime_error const& e)
693 {
694 std::string const expected = "'001' is not a number";
695 BEAST_EXPECT(e.what() == expected);
696 }
697
698 try
699 {
700 auto _ = amountFromJson(sfNumber, "000.0");
701 BEAST_EXPECT(false);
702 }
703 catch (std::runtime_error const& e)
704 {
705 std::string const expected = "'000.0' is not a number";
706 BEAST_EXPECT(e.what() == expected);
707 }
708
709 // We do not handle dangling dot
710 try
711 {
712 auto _ = amountFromJson(sfNumber, ".1");
713 BEAST_EXPECT(false);
714 }
715 catch (std::runtime_error const& e)
716 {
717 std::string const expected = "'.1' is not a number";
718 BEAST_EXPECT(e.what() == expected);
719 }
720
721 try
722 {
723 auto _ = amountFromJson(sfNumber, "1.");
724 BEAST_EXPECT(false);
725 }
726 catch (std::runtime_error const& e)
727 {
728 std::string const expected = "'1.' is not a number";
729 BEAST_EXPECT(e.what() == expected);
730 }
731
732 try
733 {
734 auto _ = amountFromJson(sfNumber, "1.e3");
735 BEAST_EXPECT(false);
736 }
737 catch (std::runtime_error const& e)
738 {
739 std::string const expected = "'1.e3' is not a number";
740 BEAST_EXPECT(e.what() == expected);
741 }
742 }
743 }
744
745 void
747 {
748 testcase("STAmount to XRPAmount conversions");
749
750 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
751 Issue const xrp{xrpIssue()};
752
753 for (std::uint64_t drops = 100000000000000000; drops != 1; drops = drops / 10)
754 {
755 auto const t = amountFromString(xrp, std::to_string(drops));
756 auto const s = t.xrp();
757 BEAST_EXPECT(s.drops() == drops);
758 BEAST_EXPECT(t == STAmount(XRPAmount(drops)));
759 BEAST_EXPECT(s == XRPAmount(drops));
760 }
761
762 try
763 {
764 auto const t = amountFromString(usd, "136500");
765 fail(to_string(t.xrp()));
766 }
767 catch (std::logic_error const&)
768 {
769 pass();
770 }
771 catch (std::exception const&)
772 {
773 fail("wrong exception");
774 }
775 }
776
777 void
779 {
780 testcase("STAmount to IOUAmount conversions");
781
782 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
783 Issue const xrp{xrpIssue()};
784
785 for (std::uint64_t dollars = 10000000000; dollars != 1; dollars = dollars / 10)
786 {
787 auto const t = amountFromString(usd, std::to_string(dollars));
788 auto const s = t.iou();
789 BEAST_EXPECT(t == STAmount(s, usd));
790 BEAST_EXPECT(s.mantissa() == t.mantissa());
791 BEAST_EXPECT(s.exponent() == t.exponent());
792 }
793
794 try
795 {
796 auto const t = amountFromString(xrp, "136500");
797 fail(to_string(t.iou()));
798 }
799 catch (std::logic_error const&)
800 {
801 pass();
802 }
803 catch (std::exception const&)
804 {
805 fail("wrong exception");
806 }
807 }
808
809 void
811 {
812 testcase("can add xrp");
813
814 // Adding zero
815 {
816 STAmount const amt1(XRPAmount(0));
817 STAmount const amt2(XRPAmount(1000));
818 BEAST_EXPECT(canAdd(amt1, amt2) == true);
819 }
820
821 // Adding zero
822 {
823 STAmount const amt1(XRPAmount(1000));
824 STAmount const amt2(XRPAmount(0));
825 BEAST_EXPECT(canAdd(amt1, amt2) == true);
826 }
827
828 // Adding two positive XRP amounts
829 {
830 STAmount const amt1(XRPAmount(500));
831 STAmount const amt2(XRPAmount(1500));
832 BEAST_EXPECT(canAdd(amt1, amt2) == true);
833 }
834
835 // Adding two negative XRP amounts
836 {
837 STAmount const amt1(XRPAmount(-500));
838 STAmount const amt2(XRPAmount(-1500));
839 BEAST_EXPECT(canAdd(amt1, amt2) == true);
840 }
841
842 // Adding a positive and a negative XRP amount
843 {
844 STAmount const amt1(XRPAmount(1000));
845 STAmount const amt2(XRPAmount(-1000));
846 BEAST_EXPECT(canAdd(amt1, amt2) == true);
847 }
848
849 // Overflow check for max XRP amounts
850 {
852 STAmount const amt2(XRPAmount(1));
853 BEAST_EXPECT(canAdd(amt1, amt2) == false);
854 }
855
856 // Overflow check for min XRP amounts
857 {
859 amt1 += XRPAmount(1);
860 STAmount const amt2(XRPAmount(-1));
861 BEAST_EXPECT(canAdd(amt1, amt2) == false);
862 }
863 }
864
865 void
867 {
868 testcase("can add iou");
869
870 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
871 Issue const eur{Currency(0x4555520000000000), AccountID(0x4985601)};
872
873 // Adding two IOU amounts
874 {
875 STAmount const amt1(usd, 500);
876 STAmount const amt2(usd, 1500);
877 BEAST_EXPECT(canAdd(amt1, amt2) == true);
878 }
879
880 // Adding a positive and a negative IOU amount
881 {
882 STAmount const amt1(usd, 1000);
883 STAmount const amt2(usd, -1000);
884 BEAST_EXPECT(canAdd(amt1, amt2) == true);
885 }
886
887 // Overflow check for max IOU amounts
888 {
890 STAmount const amt2(usd, 1);
891 BEAST_EXPECT(canAdd(amt1, amt2) == false);
892 }
893
894 // Overflow check for min IOU amounts
895 {
897 STAmount const amt2(usd, -1);
898 BEAST_EXPECT(canAdd(amt1, amt2) == false);
899 }
900
901 // Adding XRP and IOU
902 {
903 STAmount const amt1(XRPAmount(1));
904 STAmount const amt2(usd, 1);
905 BEAST_EXPECT(canAdd(amt1, amt2) == false);
906 }
907
908 // Adding different IOU issues (non zero)
909 {
910 STAmount const amt1(usd, 1000);
911 STAmount const amt2(eur, 500);
912 BEAST_EXPECT(canAdd(amt1, amt2) == false);
913 }
914
915 // Adding different IOU issues (zero)
916 {
917 STAmount const amt1(usd, 0);
918 STAmount const amt2(eur, 500);
919 BEAST_EXPECT(canAdd(amt1, amt2) == false);
920 }
921 }
922
923 void
925 {
926 testcase("can add mpt");
927
928 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
929 MPTIssue const mpt2{MPTIssue{makeMptID(2, AccountID(0x4985601))}};
930
931 // Adding zero
932 {
933 STAmount const amt1(mpt, 0);
934 STAmount const amt2(mpt, 1000);
935 BEAST_EXPECT(canAdd(amt1, amt2) == true);
936 }
937
938 // Adding zero
939 {
940 STAmount const amt1(mpt, 1000);
941 STAmount const amt2(mpt, 0);
942 BEAST_EXPECT(canAdd(amt1, amt2) == true);
943 }
944
945 // Adding two positive MPT amounts
946 {
947 STAmount const amt1(mpt, 500);
948 STAmount const amt2(mpt, 1500);
949 BEAST_EXPECT(canAdd(amt1, amt2) == true);
950 }
951
952 // Adding two negative MPT amounts
953 {
954 STAmount const amt1(mpt, -500);
955 STAmount const amt2(mpt, -1500);
956 BEAST_EXPECT(canAdd(amt1, amt2) == true);
957 }
958
959 // Adding a positive and a negative MPT amount
960 {
961 STAmount const amt1(mpt, 1000);
962 STAmount const amt2(mpt, -1000);
963 BEAST_EXPECT(canAdd(amt1, amt2) == true);
964 }
965
966 // Overflow check for max MPT amounts
967 {
969 STAmount const amt2(mpt, 1);
970 BEAST_EXPECT(canAdd(amt1, amt2) == false);
971 }
972
973 // Overflow check for min MPT amounts
974 // Note: Cannot check min MPT overflow because you cannot initialize the
975 // STAmount with a negative MPT amount.
976
977 // Adding MPT and XRP
978 {
979 STAmount const amt1(XRPAmount(1000));
980 STAmount const amt2(mpt, 1000);
981 BEAST_EXPECT(canAdd(amt1, amt2) == false);
982 }
983
984 // Adding different MPT issues (non zero)
985 {
986 STAmount const amt1(mpt2, 500);
987 STAmount const amt2(mpt, 500);
988 BEAST_EXPECT(canAdd(amt1, amt2) == false);
989 }
990
991 // Adding different MPT issues (non zero)
992 {
993 STAmount const amt1(mpt2, 0);
994 STAmount const amt2(mpt, 500);
995 BEAST_EXPECT(canAdd(amt1, amt2) == false);
996 }
997 }
998
999 void
1001 {
1002 testcase("MPT transfer rate rounding uses Number arithmetic");
1003
1004 MPTIssue const asset{makeMptID(1, AccountID(0x4985601))};
1005 Rate const transferRate{1'500'000'000};
1006 STAmount const largeAmount{asset, UINT64_C(1'230'000'000'000'000'000)};
1007 STAmount const scaledAmount{asset, UINT64_C(1'845'000'000'000'000'000)};
1008
1009 auto rules = [](bool const mptV2) {
1010 // Rules keeps a reference to the presets set, so use static
1011 // storage here rather than a local temporary.
1012 static std::unordered_set<uint256, beast::Uhash<>> const kNoFeatures;
1013 static std::unordered_set<uint256, beast::Uhash<>> const kMptV2Features{
1014 featureMPTokensV2};
1015 return Rules{mptV2 ? kMptV2Features : kNoFeatures};
1016 };
1017
1018 auto throwsOverflow = [&](auto&& f, bool expected = true) {
1019 bool threw = false;
1020 try
1021 {
1022 f();
1023 }
1024 catch (std::overflow_error const&)
1025 {
1026 threw = true;
1027 }
1028 BEAST_EXPECT(threw == expected);
1029 };
1030
1031 {
1032 CurrentTransactionRulesGuard const rg(rules(false));
1033
1034 throwsOverflow([&] { (void)multiplyRound(largeAmount, transferRate, asset, true); });
1035 throwsOverflow([&] { (void)divideRound(scaledAmount, transferRate, asset, true); });
1036 }
1037
1038 {
1039 CurrentTransactionRulesGuard const rg(rules(true));
1040
1041 throwsOverflow(
1042 [&] { (void)multiplyRound(largeAmount, transferRate, asset, true); }, false);
1043 throwsOverflow(
1044 [&] { (void)divideRound(scaledAmount, transferRate, asset, true); }, false);
1045 }
1046
1047 {
1048 CurrentTransactionRulesGuard const rg(rules(true));
1049 STAmount const one{asset, 1};
1050 STAmount const two{asset, 2};
1051
1052 BEAST_EXPECT(multiplyRound(one, transferRate, asset, true) == two);
1053 BEAST_EXPECT(multiplyRound(one, transferRate, asset, false) == one);
1054 BEAST_EXPECT(divideRound(two, transferRate, asset, true) == two);
1055 BEAST_EXPECT(divideRound(two, transferRate, asset, false) == one);
1056
1057 BEAST_EXPECT(multiplyRound(largeAmount, transferRate, asset, true) == scaledAmount);
1058 BEAST_EXPECT(divideRound(scaledAmount, transferRate, asset, true) == largeAmount);
1059 }
1060
1061 {
1062 // mulRound with an integral (XRP) operand whose mantissa is below
1063 // kMinValue exercises the legacy value-scaling loop that normalizes
1064 // the mantissa before multiply. The MPTokensV2 Number path is
1065 // not taken here because the target asset is an IOU.
1066 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
1067 STAmount const iouVal{usd, 5};
1068 STAmount const xrpVal{XRPAmount{7}}; // integral, mantissa < kMinValue
1069
1070 auto const up = mulRound(iouVal, xrpVal, usd, /*roundUp*/ true);
1071 auto const down = mulRound(iouVal, xrpVal, usd, /*roundUp*/ false);
1072 BEAST_EXPECT(down.signum() > 0);
1073 BEAST_EXPECT(up >= down);
1074 }
1075 }
1076
1077 void
1079 {
1080 testcase("can subtract xrp");
1081
1082 // Subtracting zero
1083 {
1084 STAmount const amt1(XRPAmount(1000));
1085 STAmount const amt2(XRPAmount(0));
1086 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1087 }
1088
1089 // Subtracting zero
1090 {
1091 STAmount const amt1(XRPAmount(0));
1092 STAmount const amt2(XRPAmount(1000));
1093 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1094 }
1095
1096 // Subtracting two positive XRP amounts
1097 {
1098 STAmount const amt1(XRPAmount(1500));
1099 STAmount const amt2(XRPAmount(500));
1100 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1101 }
1102
1103 // Subtracting two negative XRP amounts
1104 {
1105 STAmount const amt1(XRPAmount(-1500));
1106 STAmount const amt2(XRPAmount(-500));
1107 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1108 }
1109
1110 // Subtracting a positive and a negative XRP amount
1111 {
1112 STAmount const amt1(XRPAmount(1000));
1113 STAmount const amt2(XRPAmount(-1000));
1114 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1115 }
1116
1117 // Underflow check for min XRP amounts
1118 {
1120 amt1 += XRPAmount(1);
1121 STAmount const amt2(XRPAmount(1));
1122 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1123 }
1124
1125 // Overflow check for max XRP amounts
1126 {
1128 STAmount const amt2(XRPAmount(-1));
1129 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1130 }
1131 }
1132
1133 void
1135 {
1136 testcase("can subtract iou");
1137 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
1138 Issue const eur{Currency(0x4555520000000000), AccountID(0x4985601)};
1139
1140 // Subtracting two IOU amounts
1141 {
1142 STAmount const amt1(usd, 1500);
1143 STAmount const amt2(usd, 500);
1144 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1145 }
1146
1147 // Subtracting XRP and IOU
1148 {
1149 STAmount const amt1(XRPAmount(1000));
1150 STAmount const amt2(usd, 1000);
1151 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1152 }
1153
1154 // Subtracting different IOU issues (non zero)
1155 {
1156 STAmount const amt1(usd, 1000);
1157 STAmount const amt2(eur, 500);
1158 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1159 }
1160
1161 // Subtracting different IOU issues (zero)
1162 {
1163 STAmount const amt1(usd, 0);
1164 STAmount const amt2(eur, 500);
1165 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1166 }
1167 }
1168
1169 void
1171 {
1172 testcase("can subtract mpt");
1173
1174 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
1175 MPTIssue const mpt2{MPTIssue{makeMptID(2, AccountID(0x4985601))}};
1176
1177 // Subtracting zero
1178 {
1179 STAmount const amt1(mpt, 1000);
1180 STAmount const amt2(mpt, 0);
1181 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1182 }
1183
1184 // Subtracting zero
1185 {
1186 STAmount const amt1(mpt, 0);
1187 STAmount const amt2(mpt, 1000);
1188 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1189 }
1190
1191 // Subtracting two positive MPT amounts
1192 {
1193 STAmount const amt1(mpt, 1500);
1194 STAmount const amt2(mpt, 500);
1195 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1196 }
1197
1198 // Subtracting two negative MPT amounts
1199 {
1200 STAmount const amt1(mpt, -1500);
1201 STAmount const amt2(mpt, -500);
1202 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1203 }
1204
1205 // Subtracting a positive and a negative MPT amount
1206 {
1207 STAmount const amt1(mpt, 1000);
1208 STAmount const amt2(mpt, -1000);
1209 BEAST_EXPECT(canSubtract(amt1, amt2) == true);
1210 }
1211
1212 // Underflow check for min MPT amounts
1213 // Note: Cannot check min MPT underflow because you cannot initialize
1214 // the STAmount with a negative MPT amount.
1215
1216 // Overflow check for max positive MPT amounts (should fail)
1217 {
1219 STAmount const amt2(mpt, -2);
1220 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1221 }
1222
1223 // Subtracting MPT and XRP
1224 {
1225 STAmount const amt1(XRPAmount(1000));
1226 STAmount const amt2(mpt, 1000);
1227 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1228 }
1229
1230 // Subtracting different MPT issues (non zero)
1231 {
1232 STAmount const amt1(mpt, 1000);
1233 STAmount const amt2(mpt2, 500);
1234 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1235 }
1236
1237 // Subtracting different MPT issues (zero)
1238 {
1239 STAmount const amt1(mpt, 0);
1240 STAmount const amt2(mpt2, 500);
1241 BEAST_EXPECT(canSubtract(amt1, amt2) == false);
1242 }
1243 }
1244
1245 void
1247 {
1248 testcase("isZeroAtScale");
1249
1250 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
1251
1252 // IOU: 10 IOU — mantissa = kMinValue (10^15), exponent = -14.
1253 // One ULP at this scale is 10^-14; half-ULP is 5*10^-15.
1254 {
1255 STAmount const ref{usd, STAmount::kMinValue, -14};
1256 int const refScale = ref.exponent(); // -14
1257 BEAST_EXPECT(refScale == -14);
1258
1259 // Zero rounds to zero at any scale.
1260 STAmount const iouZero{usd, 0};
1261 BEAST_EXPECT(iouZero.isZeroAtScale(refScale));
1262
1263 // Sub-ULP: 1e-16 IOU (mantissa = kMinValue, exponent = -31).
1264 // Far below half-ULP → rounds to zero.
1265 STAmount const subUlp{usd, STAmount::kMinValue, -31};
1266 BEAST_EXPECT(subUlp.isZeroAtScale(refScale));
1267
1268 // One ULP: 1e-14 IOU (mantissa = kMinValue, exponent = -29).
1269 // Exactly the smallest representable unit at refScale → not zero.
1270 STAmount const oneUlp{usd, STAmount::kMinValue, -29};
1271 BEAST_EXPECT(!oneUlp.isZeroAtScale(refScale));
1272
1273 // The reference value itself: exponent == scale → returned
1274 // unchanged → not zero.
1275 BEAST_EXPECT(!ref.isZeroAtScale(refScale));
1276
1277 // A much larger value: certainly not zero at this scale.
1278 STAmount const large{usd, STAmount::kMinValue, 0}; // 1e15 IOU
1279 BEAST_EXPECT(!large.isZeroAtScale(refScale));
1280
1281 // When scale equals the value's own exponent, roundToScale
1282 // short-circuits and returns the value unchanged.
1283 BEAST_EXPECT(!subUlp.isZeroAtScale(subUlp.exponent()));
1284 BEAST_EXPECT(!oneUlp.isZeroAtScale(oneUlp.exponent()));
1285
1286 // Half-ULP boundary. roundToScale forms (value + ref) - ref
1287 // where ref = 10 IOU has mantissa 1e15 (LSB 0, even).
1288 // Number's default rounding is to-nearest-even, so an exact
1289 // half-ULP tie rounds toward the even-LSB neighbour — the
1290 // reference itself — and the round-trip result is zero.
1291 // Just below half-ULP rounds the same way; just above
1292 // clears half-ULP and bumps the mantissa to 1e15 + 1.
1293 STAmount const justBelowHalf{usd, STAmount::kMinValue * 4, -30};
1294 BEAST_EXPECT(justBelowHalf.isZeroAtScale(refScale));
1295
1296 STAmount const halfUlp{usd, STAmount::kMinValue * 5, -30};
1297 BEAST_EXPECT(halfUlp.isZeroAtScale(refScale));
1298
1299 STAmount const justAboveHalf{usd, STAmount::kMinValue * 6, -30};
1300 BEAST_EXPECT(!justAboveHalf.isZeroAtScale(refScale));
1301
1302 // Large magnitude gap: dust value far below an enormous scale.
1303 // 1e-80 with scale +15 — the value vanishes utterly.
1304 STAmount const dust{usd, STAmount::kMinValue, -95};
1305 BEAST_EXPECT(dust.isZeroAtScale(15));
1306
1307 // Negative values mirror positive behaviour.
1308 STAmount const negSubUlp{usd, STAmount::kMinValue, -31, true};
1309 BEAST_EXPECT(negSubUlp.isZeroAtScale(refScale));
1310
1311 STAmount const negOneUlp{usd, STAmount::kMinValue, -29, true};
1312 BEAST_EXPECT(!negOneUlp.isZeroAtScale(refScale));
1313 }
1314
1315 // XRP is integral — roundToScale short-circuits, value is preserved.
1316 {
1317 STAmount const xrp{XRPAmount{1}};
1318 BEAST_EXPECT(!xrp.isZeroAtScale(-14));
1319 BEAST_EXPECT(!xrp.isZeroAtScale(0));
1320
1321 STAmount const xrpZero{XRPAmount{0}};
1322 BEAST_EXPECT(xrpZero.isZeroAtScale(-14));
1323 }
1324
1325 // MPT is integral — same short-circuit behaviour as XRP.
1326 {
1327 MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
1328 STAmount const mptAmt{mpt, 1};
1329 BEAST_EXPECT(!mptAmt.isZeroAtScale(0));
1330 BEAST_EXPECT(!mptAmt.isZeroAtScale(-14));
1331
1332 STAmount const mptZero{mpt, 0};
1333 BEAST_EXPECT(mptZero.isZeroAtScale(0));
1334 }
1335 }
1336
1337 //--------------------------------------------------------------------------
1338
1339 void
1340 run() override
1341 {
1342 testSetValue();
1346 testUnderflow();
1347 testRounding();
1348 testParseJson();
1351 testCanAddXRP();
1352 testCanAddIOU();
1353 testCanAddMPT();
1359 }
1360};
1361
1363
1364} // namespace xrpl
A testsuite class.
Definition suite.h:52
bool unexpected(Condition shouldBeFalse, String const &reason)
DEPRECATED.
Definition suite.h:516
void pass()
Record a successful test condition.
Definition suite.h:532
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:554
LogOs< char > log
Logging output stream.
Definition suite.h:150
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Represents a JSON value.
Definition json_value.h:117
RAII class to set and restore the current transaction rules.
Definition Rules.h:113
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
Rules controlling protocol behavior.
Definition Rules.h:40
static STAmount roundSelf(STAmount const &amount)
void testSetValue(std::string const &value, Issue const &issue, bool success=true)
void run() override
Runs the suite.
void mulTest(int a, int b)
static STAmount serializeAndDeserialize(STAmount const &s)
void roundTest(int n, int d, int m)
constexpr TIss const & get() const
std::string getFullText() const override
Definition STAmount.cpp:636
void add(Serializer &s) const override
Definition STAmount.cpp:742
std::uint64_t mantissa() const noexcept
Definition STAmount.h:490
std::string getText() const override
Definition STAmount.cpp:646
static constexpr int kMinOffset
Definition STAmount.h:61
SerializedTypeID getSType() const override
Definition STAmount.cpp:630
bool negative() const noexcept
Definition STAmount.h:484
bool isDefault() const override
Definition STAmount.cpp:797
bool native() const noexcept
Definition STAmount.h:471
Asset const & asset() const
Definition STAmount.h:496
int exponent() const noexcept
Definition STAmount.h:459
bool isZeroAtScale(int scale) const
Checks if this amount evaluates to zero when constrained to a specific accounting scale.
static constexpr std::uint64_t kMinValue
Definition STAmount.h:65
static constexpr std::uint64_t kMaxValue
Definition STAmount.h:67
static constexpr std::uint64_t kMaxNative
Definition STAmount.h:69
static constexpr int kMaxOffset
Definition STAmount.h:62
STAmount const & value() const noexcept
Definition STAmount.h:610
Slice slice() const noexcept
Definition Serializer.h:45
T is_convertible_v
T max(T... args)
T min(T... args)
constexpr Zero kZero
Definition Zero.h:30
unsigned int UInt
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
SField const sfGeneric
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
STAmount amountFromString(Asset const &asset, std::string const &amount)
Definition STAmount.cpp:907
bool toCurrency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:65
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
bool canAdd(STAmount const &amt1, STAmount const &amt2)
Safely checks if two STAmount values can be added without overflow, underflow, or precision loss.
Definition STAmount.cpp:464
STAmount amountFromQuality(std::uint64_t rate)
Definition STAmount.cpp:895
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition Rate2.cpp:45
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:422
STAmount amountFromJson(SField const &name, json::Value const &v)
Definition STAmount.cpp:916
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:184
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
bool canSubtract(STAmount const &amt1, STAmount const &amt2)
Determines if it is safe to subtract one STAmount from another.
Definition STAmount.cpp:541
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:118
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
STAmount divideRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition Rate2.cpp:80
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, xrpl)
Represents a transfer rate.
Definition Rate.h:21
T to_string(T... args)
T what(T... args)