Add optional enforcement of valid integer range to Number

This commit is contained in:
Ed Hennis
2025-11-04 20:44:45 -05:00
parent 173f9f7bb0
commit 3cb447a4fe
3 changed files with 317 additions and 7 deletions

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SystemParameters.h>
#include <sstream>
#include <tuple>
@@ -725,6 +726,172 @@ public:
BEAST_EXPECT(Number(-100, -30000).truncate() == Number(0, 0));
}
void
testInteger()
{
testcase("Integer enforcement");
using namespace std::string_literals;
{
Number a{100};
BEAST_EXPECT(a.integerEnforcement() == Number::none);
BEAST_EXPECT(a.valid());
a = Number{1, 30};
BEAST_EXPECT(a.valid());
a = -100;
BEAST_EXPECT(a.valid());
}
{
Number a{100, Number::weak};
BEAST_EXPECT(a.integerEnforcement() == Number::weak);
BEAST_EXPECT(a.valid());
a = Number{1, 30, Number::none};
BEAST_EXPECT(!a.valid());
a = -100;
BEAST_EXPECT(a.integerEnforcement() == Number::weak);
BEAST_EXPECT(a.valid());
a = Number{5, Number::strong};
BEAST_EXPECT(a.integerEnforcement() == Number::strong);
BEAST_EXPECT(a.valid());
}
{
Number a{100, Number::strong};
BEAST_EXPECT(a.integerEnforcement() == Number::strong);
BEAST_EXPECT(a.valid());
try
{
a = Number{1, 30};
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == "Number::operator= integer overflow"s);
// The throw is done _after_ the number is updated.
BEAST_EXPECT((a == Number{1, 30}));
}
BEAST_EXPECT(!a.valid());
a = -100;
BEAST_EXPECT(a.integerEnforcement() == Number::strong);
BEAST_EXPECT(a.valid());
}
{
Number a{INITIAL_XRP.drops(), Number::weak};
BEAST_EXPECT(!a.valid());
a = -a;
BEAST_EXPECT(!a.valid());
try
{
a.setIntegerEnforcement(Number::strong);
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(
e.what() ==
"Number::setIntegerEnforcement integer overflow"s);
// The throw is internal to the operator before the result is
// assigned to the Number
BEAST_EXPECT(a == -INITIAL_XRP);
BEAST_EXPECT(!a.valid());
}
try
{
++a;
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == "Number::addition integer overflow"s);
// The throw is internal to the operator before the result is
// assigned to the Number
BEAST_EXPECT(a == -INITIAL_XRP);
BEAST_EXPECT(!a.valid());
}
a = Number::maxIntValue;
try
{
++a;
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == "Number::addition integer overflow"s);
// This time, the throw is done _after_ the number is updated.
BEAST_EXPECT(a == Number::maxIntValue + 1);
BEAST_EXPECT(!a.valid());
}
a = -Number::maxIntValue;
try
{
--a;
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == "Number::addition integer overflow"s);
// This time, the throw is done _after_ the number is updated.
BEAST_EXPECT(a == -Number::maxIntValue - 1);
BEAST_EXPECT(!a.valid());
}
a = Number(1, 10);
try
{
a *= Number(1, 10);
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(
e.what() == "Number::multiplication integer overflow"s);
// The throw is done _after_ the number is updated.
BEAST_EXPECT((a == Number{1, 20}));
BEAST_EXPECT(!a.valid());
}
try
{
a = Number::maxIntValue * 2;
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == "Number::operator= integer overflow"s);
// The throw is done _after_ the number is updated.
BEAST_EXPECT((a == Number{2, 14}));
BEAST_EXPECT(!a.valid());
}
try
{
a = Number(3, 15, Number::strong);
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == "Number::Number integer overflow"s);
// The Number doesn't get updated because the ctor throws
BEAST_EXPECT((a == Number{2, 14}));
BEAST_EXPECT(!a.valid());
}
a = Number(1, 10);
try
{
a /= Number(1, -10);
BEAST_EXPECT(false);
}
catch (std::overflow_error const& e)
{
BEAST_EXPECT(e.what() == "Number::division integer overflow"s);
// The throw is done _after_ the number is updated.
BEAST_EXPECT((a == Number{1, 20}));
BEAST_EXPECT(!a.valid());
}
a /= Number(1, 15);
BEAST_EXPECT((a == Number{1, 5}));
BEAST_EXPECT(a.valid());
}
}
void
run() override
{
@@ -746,6 +913,7 @@ public:
test_inc_dec();
test_toSTAmount();
test_truncate();
testInteger();
}
};