Integrate STNumber with STParsedJSON

This commit is contained in:
John Freeman
2024-11-07 10:49:57 -06:00
committed by Bronek Kozicki
parent 49e0d54c76
commit 1a032f04e3
7 changed files with 156 additions and 75 deletions

View File

@@ -417,7 +417,7 @@ STAmount
amountFromQuality(std::uint64_t rate); amountFromQuality(std::uint64_t rate);
STAmount STAmount
amountFromString(Asset const& issue, std::string const& amount); amountFromString(Asset const& asset, std::string const& amount);
STAmount STAmount
amountFromJson(SField const& name, Json::Value const& v); amountFromJson(SField const& name, Json::Value const& v);

View File

@@ -83,6 +83,19 @@ private:
std::ostream& std::ostream&
operator<<(std::ostream& out, STNumber const& rhs); operator<<(std::ostream& out, STNumber const& rhs);
struct NumberParts
{
std::uint64_t mantissa = 0;
int exponent = 0;
bool negative = false;
};
NumberParts
partsFromString(std::string const& number);
STNumber
numberFromJson(SField const& field, Json::Value const& value);
} // namespace ripple } // namespace ripple
#endif #endif

View File

@@ -20,15 +20,14 @@
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h> #include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/protocol/Protocol.h> #include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/SystemParameters.h> #include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/UintTypes.h> #include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/multiprecision/cpp_int.hpp> #include <boost/multiprecision/cpp_int.hpp>
#include <boost/regex.hpp>
#include <iterator> #include <iterator>
#include <memory> #include <memory>
@@ -833,75 +832,16 @@ amountFromQuality(std::uint64_t rate)
STAmount STAmount
amountFromString(Asset const& asset, std::string const& amount) amountFromString(Asset const& asset, std::string const& amount)
{ {
static boost::regex const reNumber( auto const parts = partsFromString(amount);
"^" // the beginning of the string if ((asset.native() || asset.holds<MPTIssue>()) && parts.exponent < 0)
"([-+]?)" // (optional) + or - character
"(0|[1-9][0-9]*)" // a number (no leading zeroes, unless 0)
"(\\.([0-9]+))?" // (optional) period followed by any number
"([eE]([+-]?)([0-9]+))?" // (optional) E, optional + or -, any number
"$",
boost::regex_constants::optimize);
boost::smatch match;
if (!boost::regex_match(amount, match, reNumber))
Throw<std::runtime_error>("Number '" + amount + "' is not valid");
// Match fields:
// 0 = whole input
// 1 = sign
// 2 = integer portion
// 3 = whole fraction (with '.')
// 4 = fraction (without '.')
// 5 = whole exponent (with 'e')
// 6 = exponent sign
// 7 = exponent number
// CHECKME: Why 32? Shouldn't this be 16?
if ((match[2].length() + match[4].length()) > 32)
Throw<std::runtime_error>("Number '" + amount + "' is overlong");
bool negative = (match[1].matched && (match[1] == "-"));
// Can't specify XRP or MPT using fractional representation
if ((asset.native() || asset.holds<MPTIssue>()) && match[3].matched)
Throw<std::runtime_error>( Throw<std::runtime_error>(
"XRP and MPT must be specified as integral amount."); "XRP and MPT must be specified as integral amount.");
return {asset, parts.mantissa, parts.exponent, parts.negative};
std::uint64_t mantissa;
int exponent;
if (!match[4].matched) // integer only
{
mantissa =
beast::lexicalCastThrow<std::uint64_t>(std::string(match[2]));
exponent = 0;
}
else
{
// integer and fraction
mantissa = beast::lexicalCastThrow<std::uint64_t>(match[2] + match[4]);
exponent = -(match[4].length());
}
if (match[5].matched)
{
// we have an exponent
if (match[6].matched && (match[6] == "-"))
exponent -= beast::lexicalCastThrow<int>(std::string(match[7]));
else
exponent += beast::lexicalCastThrow<int>(std::string(match[7]));
}
return {asset, mantissa, exponent, negative};
} }
STAmount STAmount
amountFromJson(SField const& name, Json::Value const& v) amountFromJson(SField const& name, Json::Value const& v)
{ {
STAmount::mantissa_type mantissa = 0;
STAmount::exponent_type exponent = 0;
bool negative = false;
Asset asset; Asset asset;
Json::Value value; Json::Value value;
@@ -993,36 +933,38 @@ amountFromJson(SField const& name, Json::Value const& v)
} }
} }
NumberParts parts;
if (value.isInt()) if (value.isInt())
{ {
if (value.asInt() >= 0) if (value.asInt() >= 0)
{ {
mantissa = value.asInt(); parts.mantissa = value.asInt();
} }
else else
{ {
mantissa = -value.asInt(); parts.mantissa = -value.asInt();
negative = true; parts.negative = true;
} }
} }
else if (value.isUInt()) else if (value.isUInt())
{ {
mantissa = v.asUInt(); parts.mantissa = v.asUInt();
} }
else if (value.isString()) else if (value.isString())
{ {
auto const ret = amountFromString(asset, value.asString()); parts = partsFromString(value.asString());
// Can't specify XRP or MPT using fractional representation
mantissa = ret.mantissa(); if ((asset.native() || asset.holds<MPTIssue>()) && parts.exponent < 0)
exponent = ret.exponent(); Throw<std::runtime_error>(
negative = ret.negative(); "XRP and MPT must be specified as integral amount.");
} }
else else
{ {
Throw<std::runtime_error>("invalid amount type"); Throw<std::runtime_error>("invalid amount type");
} }
return {name, asset, mantissa, exponent, negative}; return {name, asset, parts.mantissa, parts.exponent, parts.negative};
} }
bool bool

View File

@@ -19,8 +19,10 @@
#include <xrpl/protocol/STNumber.h> #include <xrpl/protocol/STNumber.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/SField.h> #include <xrpl/protocol/SField.h>
#include <boost/regex.hpp>
namespace ripple { namespace ripple {
@@ -108,4 +110,101 @@ operator<<(std::ostream& out, STNumber const& rhs)
return out << rhs.getText(); return out << rhs.getText();
} }
NumberParts
partsFromString(std::string const& number)
{
static boost::regex const reNumber(
"^" // the beginning of the string
"([-+]?)" // (optional) + or - character
"(0|[1-9][0-9]*)" // a number (no leading zeroes, unless 0)
"(\\.([0-9]+))?" // (optional) period followed by any number
"([eE]([+-]?)([0-9]+))?" // (optional) E, optional + or -, any number
"$",
boost::regex_constants::optimize);
boost::smatch match;
if (!boost::regex_match(number, match, reNumber))
Throw<std::runtime_error>("'" + number + "' is not a number");
// Match fields:
// 0 = whole input
// 1 = sign
// 2 = integer portion
// 3 = whole fraction (with '.')
// 4 = fraction (without '.')
// 5 = whole exponent (with 'e')
// 6 = exponent sign
// 7 = exponent number
bool negative = (match[1].matched && (match[1] == "-"));
std::uint64_t mantissa;
int exponent;
if (!match[4].matched) // integer only
{
mantissa =
beast::lexicalCastThrow<std::uint64_t>(std::string(match[2]));
exponent = 0;
}
else
{
// integer and fraction
mantissa = beast::lexicalCastThrow<std::uint64_t>(match[2] + match[4]);
exponent = -(match[4].length());
}
if (match[5].matched)
{
// we have an exponent
if (match[6].matched && (match[6] == "-"))
exponent -= beast::lexicalCastThrow<int>(std::string(match[7]));
else
exponent += beast::lexicalCastThrow<int>(std::string(match[7]));
}
return {mantissa, exponent, negative};
}
STNumber
numberFromJson(SField const& field, Json::Value const& value)
{
NumberParts parts;
if (value.isInt())
{
if (value.asInt() >= 0)
{
parts.mantissa = value.asInt();
}
else
{
parts.mantissa = -value.asInt();
parts.negative = true;
}
}
else if (value.isUInt())
{
parts.mantissa = value.asUInt();
}
else if (value.isString())
{
parts = partsFromString(value.asString());
// Only strings can represent out-of-range values.
if (parts.mantissa > std::numeric_limits<std::int64_t>::max())
Throw<std::range_error>("too high");
}
else
{
Throw<std::runtime_error>("not a number");
}
std::int64_t mantissa = parts.mantissa;
if (parts.negative)
mantissa = -mantissa;
return STNumber{field, Number{mantissa, parts.exponent}};
}
} // namespace ripple } // namespace ripple

View File

@@ -32,6 +32,7 @@
#include <xrpl/protocol/STBlob.h> #include <xrpl/protocol/STBlob.h>
#include <xrpl/protocol/STInteger.h> #include <xrpl/protocol/STInteger.h>
#include <xrpl/protocol/STIssue.h> #include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/STParsedJSON.h> #include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/STPathSet.h> #include <xrpl/protocol/STPathSet.h>
#include <xrpl/protocol/STVector256.h> #include <xrpl/protocol/STVector256.h>
@@ -574,6 +575,20 @@ parseLeaf(
break; break;
case STI_NUMBER:
try
{
ret =
detail::make_stvar<STNumber>(numberFromJson(field, value));
}
catch (std::exception const&)
{
error = invalid_data(json_name, fieldName);
return ret;
}
break;
case STI_VECTOR256: case STI_VECTOR256:
if (!value.isArrayOrNull()) if (!value.isArrayOrNull())
{ {

View File

@@ -188,6 +188,9 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args)
case STI_AMOUNT: case STI_AMOUNT:
construct<STAmount>(std::forward<Args>(args)...); construct<STAmount>(std::forward<Args>(args)...);
return; return;
case STI_NUMBER:
construct<STNumber>(std::forward<Args>(args)...);
return;
case STI_UINT128: case STI_UINT128:
construct<STUInt128>(std::forward<Args>(args)...); construct<STUInt128>(std::forward<Args>(args)...);
return; return;

View File

@@ -78,6 +78,15 @@ struct STNumber_test : public beast::unit_test::suite
STAmount const totalAmount{totalValue, strikePrice.issue()}; STAmount const totalAmount{totalValue, strikePrice.issue()};
BEAST_EXPECT(totalAmount == Number{10'000}); BEAST_EXPECT(totalAmount == Number{10'000});
} }
{
BEAST_EXPECT(
numberFromJson(sfNumber, "123") == STNumber(sfNumber, 123));
BEAST_EXPECT(
numberFromJson(sfNumber, "3.14e2") == STNumber(sfNumber, 314));
BEAST_EXPECT(
numberFromJson(sfNumber, "1000e-2") == STNumber(sfNumber, 10));
}
} }
}; };