Browse Source

Merge pull request #6379

9cc9152 rpc: Accept scientific notation for monetary amounts in JSON (Wladimir J. van der Laan)
0.13
Wladimir J. van der Laan 10 years ago
parent
commit
c52e8b300a
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 2
      src/rpcserver.cpp
  2. 21
      src/test/rpc_tests.cpp
  3. 66
      src/test/util_tests.cpp
  4. 120
      src/utilstrencodings.cpp
  5. 7
      src/utilstrencodings.h

2
src/rpcserver.cpp

@ -124,7 +124,7 @@ CAmount AmountFromValue(const UniValue& value)
if (!value.isReal() && !value.isNum()) if (!value.isReal() && !value.isNum())
throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number"); throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number");
CAmount amount; CAmount amount;
if (!ParseMoney(value.getValStr(), amount)) if (!ParseFixedPoint(value.getValStr(), 8, &amount))
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount");
if (!MoneyRange(amount)) if (!MoneyRange(amount))
throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range"); throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range");

21
src/test/rpc_tests.cpp

@ -142,6 +142,24 @@ BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1.00000000")), 100000000LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1.00000000")), 100000000LL);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.9999999")), 2099999999999990LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.9999999")), 2099999999999990LL);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.99999999")), 2099999999999999LL); BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.99999999")), 2099999999999999LL);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1e-8")), COIN/100000000);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.1e-7")), COIN/100000000);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.01e-6")), COIN/100000000);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.0000000000000000000000000000000000000000000000000000000000000000000000000001e+68")), COIN/100000000);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("10000000000000000000000000000000000000000000000000000000000000000e-64")), COIN);
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000e64")), COIN);
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e-9")), UniValue); //should fail
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("0.000000019")), UniValue); //should fail
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000001000000")), 1LL); //should pass, cut trailing 0
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("19e-9")), UniValue); //should fail
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.19e-6")), 19); //should pass, leading 0 is present
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("92233720368.54775808")), UniValue); //overflow error
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e+11")), UniValue); //overflow error
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e11")), UniValue); //overflow error signless
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("93e+9")), UniValue); //overflow error
} }
BOOST_AUTO_TEST_CASE(json_parse_errors) BOOST_AUTO_TEST_CASE(json_parse_errors)
@ -151,6 +169,9 @@ BOOST_AUTO_TEST_CASE(json_parse_errors)
// Valid, with leading or trailing whitespace // Valid, with leading or trailing whitespace
BOOST_CHECK_EQUAL(ParseNonRFCJSONValue(" 1.0").get_real(), 1.0); BOOST_CHECK_EQUAL(ParseNonRFCJSONValue(" 1.0").get_real(), 1.0);
BOOST_CHECK_EQUAL(ParseNonRFCJSONValue("1.0 ").get_real(), 1.0); BOOST_CHECK_EQUAL(ParseNonRFCJSONValue("1.0 ").get_real(), 1.0);
BOOST_CHECK_THROW(AmountFromValue(ParseNonRFCJSONValue(".19e-6")), std::runtime_error); //should fail, missing leading 0, therefore invalid JSON
BOOST_CHECK_EQUAL(AmountFromValue(ParseNonRFCJSONValue("0.00000000000000000000000000000000000001e+30 ")), 1);
// Invalid, initial garbage // Invalid, initial garbage
BOOST_CHECK_THROW(ParseNonRFCJSONValue("[1.0"), std::runtime_error); BOOST_CHECK_THROW(ParseNonRFCJSONValue("[1.0"), std::runtime_error);
BOOST_CHECK_THROW(ParseNonRFCJSONValue("a1.0"), std::runtime_error); BOOST_CHECK_THROW(ParseNonRFCJSONValue("a1.0"), std::runtime_error);

66
src/test/util_tests.cpp

@ -418,4 +418,70 @@ BOOST_AUTO_TEST_CASE(test_FormatSubVersion)
BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments),std::string("/Test:0.9.99(comment1)/")); BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments),std::string("/Test:0.9.99(comment1)/"));
BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments2),std::string("/Test:0.9.99(comment1; comment2)/")); BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments2),std::string("/Test:0.9.99(comment1; comment2)/"));
} }
BOOST_AUTO_TEST_CASE(test_ParseFixedPoint)
{
int64_t amount = 0;
BOOST_CHECK(ParseFixedPoint("0", 8, &amount));
BOOST_CHECK_EQUAL(amount, 0LL);
BOOST_CHECK(ParseFixedPoint("1", 8, &amount));
BOOST_CHECK_EQUAL(amount, 100000000LL);
BOOST_CHECK(ParseFixedPoint("0.0", 8, &amount));
BOOST_CHECK_EQUAL(amount, 0LL);
BOOST_CHECK(ParseFixedPoint("-0.1", 8, &amount));
BOOST_CHECK_EQUAL(amount, -10000000LL);
BOOST_CHECK(ParseFixedPoint("1.1", 8, &amount));
BOOST_CHECK_EQUAL(amount, 110000000LL);
BOOST_CHECK(ParseFixedPoint("1.10000000000000000", 8, &amount));
BOOST_CHECK_EQUAL(amount, 110000000LL);
BOOST_CHECK(ParseFixedPoint("1.1e1", 8, &amount));
BOOST_CHECK_EQUAL(amount, 1100000000LL);
BOOST_CHECK(ParseFixedPoint("1.1e-1", 8, &amount));
BOOST_CHECK_EQUAL(amount, 11000000LL);
BOOST_CHECK(ParseFixedPoint("1000", 8, &amount));
BOOST_CHECK_EQUAL(amount, 100000000000LL);
BOOST_CHECK(ParseFixedPoint("-1000", 8, &amount));
BOOST_CHECK_EQUAL(amount, -100000000000LL);
BOOST_CHECK(ParseFixedPoint("0.00000001", 8, &amount));
BOOST_CHECK_EQUAL(amount, 1LL);
BOOST_CHECK(ParseFixedPoint("0.0000000100000000", 8, &amount));
BOOST_CHECK_EQUAL(amount, 1LL);
BOOST_CHECK(ParseFixedPoint("-0.00000001", 8, &amount));
BOOST_CHECK_EQUAL(amount, -1LL);
BOOST_CHECK(ParseFixedPoint("1000000000.00000001", 8, &amount));
BOOST_CHECK_EQUAL(amount, 100000000000000001LL);
BOOST_CHECK(ParseFixedPoint("9999999999.99999999", 8, &amount));
BOOST_CHECK_EQUAL(amount, 999999999999999999LL);
BOOST_CHECK(ParseFixedPoint("-9999999999.99999999", 8, &amount));
BOOST_CHECK_EQUAL(amount, -999999999999999999LL);
BOOST_CHECK(!ParseFixedPoint("", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("a-1000", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-a1000", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-1000a", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-01000", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("00.1", 8, &amount));
BOOST_CHECK(!ParseFixedPoint(".1", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("--0.1", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("0.000000001", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-0.000000001", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("0.00000001000000001", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-10000000000.00000000", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("10000000000.00000000", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-10000000000.00000001", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("10000000000.00000001", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-10000000000.00000009", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("10000000000.00000009", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-99999999999.99999999", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("99999909999.09999999", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("92233720368.54775807", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("92233720368.54775808", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-92233720368.54775808", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("-92233720368.54775809", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("1.1e", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("1.1e-", 8, &amount));
BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount));
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

120
src/utilstrencodings.cpp

@ -538,3 +538,123 @@ int atoi(const std::string& str)
{ {
return atoi(str.c_str()); return atoi(str.c_str());
} }
/** Upper bound for mantissa.
* 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit integer.
* Larger integers cannot consist of arbitrary combinations of 0-9:
*
* 999999999999999999 1^18-1
* 9223372036854775807 (1<<63)-1 (max int64_t)
* 9999999999999999999 1^19-1 (would overflow)
*/
static const int64_t UPPER_BOUND = 1000000000000000000LL - 1LL;
/** Helper function for ParseFixedPoint */
static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantissa_tzeros)
{
if(ch == '0')
++mantissa_tzeros;
else {
for (int i=0; i<=mantissa_tzeros; ++i) {
if (mantissa > (UPPER_BOUND / 10LL))
return false; /* overflow */
mantissa *= 10;
}
mantissa += ch - '0';
mantissa_tzeros = 0;
}
return true;
}
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out)
{
int64_t mantissa = 0;
int64_t exponent = 0;
int mantissa_tzeros = 0;
bool mantissa_sign = false;
bool exponent_sign = false;
int ptr = 0;
int end = val.size();
int point_ofs = 0;
if (ptr < end && val[ptr] == '-') {
mantissa_sign = true;
++ptr;
}
if (ptr < end)
{
if (val[ptr] == '0') {
/* pass single 0 */
++ptr;
} else if (val[ptr] >= '1' && val[ptr] <= '9') {
while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros))
return false; /* overflow */
++ptr;
}
} else return false; /* missing expected digit */
} else return false; /* empty string or loose '-' */
if (ptr < end && val[ptr] == '.')
{
++ptr;
if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9')
{
while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros))
return false; /* overflow */
++ptr;
++point_ofs;
}
} else return false; /* missing expected digit */
}
if (ptr < end && (val[ptr] == 'e' || val[ptr] == 'E'))
{
++ptr;
if (ptr < end && val[ptr] == '+')
++ptr;
else if (ptr < end && val[ptr] == '-') {
exponent_sign = true;
++ptr;
}
if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
if (exponent > (UPPER_BOUND / 10LL))
return false; /* overflow */
exponent = exponent * 10 + val[ptr] - '0';
++ptr;
}
} else return false; /* missing expected digit */
}
if (ptr != end)
return false; /* trailing garbage */
/* finalize exponent */
if (exponent_sign)
exponent = -exponent;
exponent = exponent - point_ofs + mantissa_tzeros;
/* finalize mantissa */
if (mantissa_sign)
mantissa = -mantissa;
/* convert to one 64-bit fixed-point value */
exponent += decimals;
if (exponent < 0)
return false; /* cannot represent values smaller than 10^-decimals */
if (exponent >= 18)
return false; /* cannot represent values larger than or equal to 10^(18-decimals) */
for (int i=0; i < exponent; ++i) {
if (mantissa > (UPPER_BOUND / 10LL) || mantissa < -(UPPER_BOUND / 10LL))
return false; /* overflow */
mantissa *= 10;
}
if (mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND)
return false; /* overflow */
if (amount_out)
*amount_out = mantissa;
return true;
}

7
src/utilstrencodings.h

@ -109,4 +109,11 @@ bool TimingResistantEqual(const T& a, const T& b)
return accumulator == 0; return accumulator == 0;
} }
/** Parse number as fixed point according to JSON number syntax.
* See http://json.org/number.gif
* @returns true on success, false on error.
* @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger.
*/
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out);
#endif // BITCOIN_UTILSTRENCODINGS_H #endif // BITCOIN_UTILSTRENCODINGS_H

Loading…
Cancel
Save