diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index 23e4c5e5b5..f1bab1bcbd 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -417,7 +417,7 @@ STAmount amountFromQuality(std::uint64_t rate); STAmount -amountFromString(Asset const& issue, std::string const& amount); +amountFromString(Asset const& asset, std::string const& amount); STAmount amountFromJson(SField const& name, Json::Value const& v); diff --git a/include/xrpl/protocol/STNumber.h b/include/xrpl/protocol/STNumber.h index c0fce572c8..bf22b9d142 100644 --- a/include/xrpl/protocol/STNumber.h +++ b/include/xrpl/protocol/STNumber.h @@ -83,6 +83,19 @@ private: std::ostream& 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 #endif diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 37830830ad..d9538a88d4 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -20,15 +20,14 @@ #include #include #include -#include #include #include +#include #include #include #include #include #include -#include #include #include @@ -833,75 +832,16 @@ amountFromQuality(std::uint64_t rate) STAmount amountFromString(Asset const& asset, std::string const& amount) { - 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(amount, match, reNumber)) - Throw("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("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()) && match[3].matched) + auto const parts = partsFromString(amount); + if ((asset.native() || asset.holds()) && parts.exponent < 0) Throw( "XRP and MPT must be specified as integral amount."); - - std::uint64_t mantissa; - int exponent; - - if (!match[4].matched) // integer only - { - mantissa = - beast::lexicalCastThrow(std::string(match[2])); - exponent = 0; - } - else - { - // integer and fraction - mantissa = beast::lexicalCastThrow(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(std::string(match[7])); - else - exponent += beast::lexicalCastThrow(std::string(match[7])); - } - - return {asset, mantissa, exponent, negative}; + return {asset, parts.mantissa, parts.exponent, parts.negative}; } STAmount amountFromJson(SField const& name, Json::Value const& v) { - STAmount::mantissa_type mantissa = 0; - STAmount::exponent_type exponent = 0; - bool negative = false; Asset asset; Json::Value value; @@ -993,36 +933,38 @@ amountFromJson(SField const& name, Json::Value const& v) } } + NumberParts parts; + if (value.isInt()) { if (value.asInt() >= 0) { - mantissa = value.asInt(); + parts.mantissa = value.asInt(); } else { - mantissa = -value.asInt(); - negative = true; + parts.mantissa = -value.asInt(); + parts.negative = true; } } else if (value.isUInt()) { - mantissa = v.asUInt(); + parts.mantissa = v.asUInt(); } else if (value.isString()) { - auto const ret = amountFromString(asset, value.asString()); - - mantissa = ret.mantissa(); - exponent = ret.exponent(); - negative = ret.negative(); + parts = partsFromString(value.asString()); + // Can't specify XRP or MPT using fractional representation + if ((asset.native() || asset.holds()) && parts.exponent < 0) + Throw( + "XRP and MPT must be specified as integral amount."); } else { Throw("invalid amount type"); } - return {name, asset, mantissa, exponent, negative}; + return {name, asset, parts.mantissa, parts.exponent, parts.negative}; } bool diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index 74961bfbca..0164bb806f 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -19,8 +19,10 @@ #include +#include #include #include +#include namespace ripple { @@ -108,4 +110,101 @@ operator<<(std::ostream& out, STNumber const& rhs) 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("'" + 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::string(match[2])); + exponent = 0; + } + else + { + // integer and fraction + mantissa = beast::lexicalCastThrow(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(std::string(match[7])); + else + exponent += beast::lexicalCastThrow(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::max()) + Throw("too high"); + } + else + { + Throw("not a number"); + } + + std::int64_t mantissa = parts.mantissa; + if (parts.negative) + mantissa = -mantissa; + + return STNumber{field, Number{mantissa, parts.exponent}}; +} + } // namespace ripple diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index 7d08993a8b..6410051bde 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -574,6 +575,20 @@ parseLeaf( break; + case STI_NUMBER: + try + { + ret = + detail::make_stvar(numberFromJson(field, value)); + } + catch (std::exception const&) + { + error = invalid_data(json_name, fieldName); + return ret; + } + + break; + case STI_VECTOR256: if (!value.isArrayOrNull()) { diff --git a/src/libxrpl/protocol/STVar.cpp b/src/libxrpl/protocol/STVar.cpp index c5d3102bfa..f997604e78 100644 --- a/src/libxrpl/protocol/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -188,6 +188,9 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args) case STI_AMOUNT: construct(std::forward(args)...); return; + case STI_NUMBER: + construct(std::forward(args)...); + return; case STI_UINT128: construct(std::forward(args)...); return; diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index ed255e32f1..6a3afc5e0a 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -78,6 +78,15 @@ struct STNumber_test : public beast::unit_test::suite STAmount const totalAmount{totalValue, strikePrice.issue()}; 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)); + } } };