Skip to content

Commit 691749f

Browse files
committed
rpc: Accept scientific notation for monetary amounts in JSON
Add a function `ParseFixedPoint` that parses numbers according to the JSON number specification and returns a 64-bit integer. Coming from btc@fed500e2dd571406b9420f7a26a5db6dee801806
1 parent b0f9051 commit 691749f

File tree

6 files changed

+252
-13
lines changed

6 files changed

+252
-13
lines changed

src/rpc/server.cpp

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,22 +117,15 @@ void RPCTypeCheckObj(const UniValue& o,
117117
}
118118
}
119119

120-
static inline int64_t roundint64(double d)
121-
{
122-
return (int64_t)(d > 0 ? d + 0.5 : d - 0.5);
123-
}
124-
125120
CAmount AmountFromValue(const UniValue& value)
126121
{
127-
if (!value.isNum())
128-
throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number");
129-
130-
double dAmount = value.get_real();
131-
if (dAmount <= 0.0 || dAmount > 21000000.0)
122+
if (!value.isNum() && !value.isStr())
123+
throw JSONRPCError(RPC_TYPE_ERROR,"Amount is not a number or string");
124+
CAmount nAmount;
125+
if (!ParseFixedPoint(value.getValStr(), 8, &nAmount))
132126
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount");
133-
CAmount nAmount = roundint64(dAmount * COIN);
134127
if (!Params().GetConsensus().MoneyRange(nAmount))
135-
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount");
128+
throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range");
136129
return nAmount;
137130
}
138131

src/test/rpc_tests.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,24 @@ BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
163163
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1.00000000")), 100000000LL);
164164
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.9999999")), 2099999999999990LL);
165165
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.99999999")), 2099999999999999LL);
166+
167+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("1e-8")), COIN/100000000);
168+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.1e-7")), COIN/100000000);
169+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.01e-6")), COIN/100000000);
170+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.0000000000000000000000000000000000000000000000000000000000000000000000000001e+68")), COIN/100000000);
171+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("10000000000000000000000000000000000000000000000000000000000000000e-64")), COIN);
172+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000e64")), COIN);
173+
174+
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e-9")), UniValue); //should fail
175+
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("0.000000019")), UniValue); //should fail
176+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.00000001000000")), 1LL); //should pass, cut trailing 0
177+
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("19e-9")), UniValue); //should fail
178+
BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("0.19e-6")), 19); //should pass, leading 0 is present
179+
180+
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("92233720368.54775808")), UniValue); //overflow error
181+
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e+11")), UniValue); //overflow error
182+
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("1e11")), UniValue); //overflow error signless
183+
BOOST_CHECK_THROW(AmountFromValue(ValueFromString("93e+9")), UniValue); //overflow error
166184
}
167185

168186
BOOST_AUTO_TEST_CASE(json_parse_errors)

src/test/util_tests.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,4 +424,70 @@ BOOST_AUTO_TEST_CASE(test_FormatSubVersion)
424424
BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments),std::string("/Test:0.9.99(comment1)/"));
425425
BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments2),std::string("/Test:0.9.99(comment1; comment2)/"));
426426
}
427+
428+
BOOST_AUTO_TEST_CASE(test_ParseFixedPoint)
429+
{
430+
int64_t amount = 0;
431+
BOOST_CHECK(ParseFixedPoint("0", 8, &amount));
432+
BOOST_CHECK_EQUAL(amount, 0LL);
433+
BOOST_CHECK(ParseFixedPoint("1", 8, &amount));
434+
BOOST_CHECK_EQUAL(amount, 100000000LL);
435+
BOOST_CHECK(ParseFixedPoint("0.0", 8, &amount));
436+
BOOST_CHECK_EQUAL(amount, 0LL);
437+
BOOST_CHECK(ParseFixedPoint("-0.1", 8, &amount));
438+
BOOST_CHECK_EQUAL(amount, -10000000LL);
439+
BOOST_CHECK(ParseFixedPoint("1.1", 8, &amount));
440+
BOOST_CHECK_EQUAL(amount, 110000000LL);
441+
BOOST_CHECK(ParseFixedPoint("1.10000000000000000", 8, &amount));
442+
BOOST_CHECK_EQUAL(amount, 110000000LL);
443+
BOOST_CHECK(ParseFixedPoint("1.1e1", 8, &amount));
444+
BOOST_CHECK_EQUAL(amount, 1100000000LL);
445+
BOOST_CHECK(ParseFixedPoint("1.1e-1", 8, &amount));
446+
BOOST_CHECK_EQUAL(amount, 11000000LL);
447+
BOOST_CHECK(ParseFixedPoint("1000", 8, &amount));
448+
BOOST_CHECK_EQUAL(amount, 100000000000LL);
449+
BOOST_CHECK(ParseFixedPoint("-1000", 8, &amount));
450+
BOOST_CHECK_EQUAL(amount, -100000000000LL);
451+
BOOST_CHECK(ParseFixedPoint("0.00000001", 8, &amount));
452+
BOOST_CHECK_EQUAL(amount, 1LL);
453+
BOOST_CHECK(ParseFixedPoint("0.0000000100000000", 8, &amount));
454+
BOOST_CHECK_EQUAL(amount, 1LL);
455+
BOOST_CHECK(ParseFixedPoint("-0.00000001", 8, &amount));
456+
BOOST_CHECK_EQUAL(amount, -1LL);
457+
BOOST_CHECK(ParseFixedPoint("1000000000.00000001", 8, &amount));
458+
BOOST_CHECK_EQUAL(amount, 100000000000000001LL);
459+
BOOST_CHECK(ParseFixedPoint("9999999999.99999999", 8, &amount));
460+
BOOST_CHECK_EQUAL(amount, 999999999999999999LL);
461+
BOOST_CHECK(ParseFixedPoint("-9999999999.99999999", 8, &amount));
462+
BOOST_CHECK_EQUAL(amount, -999999999999999999LL);
463+
464+
BOOST_CHECK(!ParseFixedPoint("", 8, &amount));
465+
BOOST_CHECK(!ParseFixedPoint("-", 8, &amount));
466+
BOOST_CHECK(!ParseFixedPoint("a-1000", 8, &amount));
467+
BOOST_CHECK(!ParseFixedPoint("-a1000", 8, &amount));
468+
BOOST_CHECK(!ParseFixedPoint("-1000a", 8, &amount));
469+
BOOST_CHECK(!ParseFixedPoint("-01000", 8, &amount));
470+
BOOST_CHECK(!ParseFixedPoint("00.1", 8, &amount));
471+
BOOST_CHECK(!ParseFixedPoint(".1", 8, &amount));
472+
BOOST_CHECK(!ParseFixedPoint("--0.1", 8, &amount));
473+
BOOST_CHECK(!ParseFixedPoint("0.000000001", 8, &amount));
474+
BOOST_CHECK(!ParseFixedPoint("-0.000000001", 8, &amount));
475+
BOOST_CHECK(!ParseFixedPoint("0.00000001000000001", 8, &amount));
476+
BOOST_CHECK(!ParseFixedPoint("-10000000000.00000000", 8, &amount));
477+
BOOST_CHECK(!ParseFixedPoint("10000000000.00000000", 8, &amount));
478+
BOOST_CHECK(!ParseFixedPoint("-10000000000.00000001", 8, &amount));
479+
BOOST_CHECK(!ParseFixedPoint("10000000000.00000001", 8, &amount));
480+
BOOST_CHECK(!ParseFixedPoint("-10000000000.00000009", 8, &amount));
481+
BOOST_CHECK(!ParseFixedPoint("10000000000.00000009", 8, &amount));
482+
BOOST_CHECK(!ParseFixedPoint("-99999999999.99999999", 8, &amount));
483+
BOOST_CHECK(!ParseFixedPoint("99999909999.09999999", 8, &amount));
484+
BOOST_CHECK(!ParseFixedPoint("92233720368.54775807", 8, &amount));
485+
BOOST_CHECK(!ParseFixedPoint("92233720368.54775808", 8, &amount));
486+
BOOST_CHECK(!ParseFixedPoint("-92233720368.54775808", 8, &amount));
487+
BOOST_CHECK(!ParseFixedPoint("-92233720368.54775809", 8, &amount));
488+
BOOST_CHECK(!ParseFixedPoint("1.1e", 8, &amount));
489+
BOOST_CHECK(!ParseFixedPoint("1.1e-", 8, &amount));
490+
BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount));
491+
}
492+
427493
BOOST_AUTO_TEST_SUITE_END()

src/utilstrencodings.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,3 +614,123 @@ int atoi(const std::string& str)
614614
{
615615
return atoi(str.c_str());
616616
}
617+
618+
619+
/** Upper bound for mantissa.
620+
* 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit integer.
621+
* Larger integers cannot consist of arbitrary combinations of 0-9:
622+
*
623+
* 999999999999999999 1^18-1
624+
* 9223372036854775807 (1<<63)-1 (max int64_t)
625+
* 9999999999999999999 1^19-1 (would overflow)
626+
*/
627+
static const int64_t UPPER_BOUND = 1000000000000000000LL - 1LL;
628+
629+
/** Helper function for ParseFixedPoint */
630+
static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantissa_tzeros)
631+
{
632+
if(ch == '0')
633+
++mantissa_tzeros;
634+
else {
635+
for (int i=0; i<=mantissa_tzeros; ++i) {
636+
if (mantissa > (UPPER_BOUND / 10LL))
637+
return false; /* overflow */
638+
mantissa *= 10;
639+
}
640+
mantissa += ch - '0';
641+
mantissa_tzeros = 0;
642+
}
643+
return true;
644+
}
645+
646+
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out)
647+
{
648+
int64_t mantissa = 0;
649+
int64_t exponent = 0;
650+
int mantissa_tzeros = 0;
651+
bool mantissa_sign = false;
652+
bool exponent_sign = false;
653+
int ptr = 0;
654+
int end = val.size();
655+
int point_ofs = 0;
656+
657+
if (ptr < end && val[ptr] == '-') {
658+
mantissa_sign = true;
659+
++ptr;
660+
}
661+
if (ptr < end)
662+
{
663+
if (val[ptr] == '0') {
664+
/* pass single 0 */
665+
++ptr;
666+
} else if (val[ptr] >= '1' && val[ptr] <= '9') {
667+
while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
668+
if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros))
669+
return false; /* overflow */
670+
++ptr;
671+
}
672+
} else return false; /* missing expected digit */
673+
} else return false; /* empty string or loose '-' */
674+
if (ptr < end && val[ptr] == '.')
675+
{
676+
++ptr;
677+
if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9')
678+
{
679+
while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
680+
if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros))
681+
return false; /* overflow */
682+
++ptr;
683+
++point_ofs;
684+
}
685+
} else return false; /* missing expected digit */
686+
}
687+
if (ptr < end && (val[ptr] == 'e' || val[ptr] == 'E'))
688+
{
689+
++ptr;
690+
if (ptr < end && val[ptr] == '+')
691+
++ptr;
692+
else if (ptr < end && val[ptr] == '-') {
693+
exponent_sign = true;
694+
++ptr;
695+
}
696+
if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
697+
while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') {
698+
if (exponent > (UPPER_BOUND / 10LL))
699+
return false; /* overflow */
700+
exponent = exponent * 10 + val[ptr] - '0';
701+
++ptr;
702+
}
703+
} else return false; /* missing expected digit */
704+
}
705+
if (ptr != end)
706+
return false; /* trailing garbage */
707+
708+
/* finalize exponent */
709+
if (exponent_sign)
710+
exponent = -exponent;
711+
exponent = exponent - point_ofs + mantissa_tzeros;
712+
713+
/* finalize mantissa */
714+
if (mantissa_sign)
715+
mantissa = -mantissa;
716+
717+
/* convert to one 64-bit fixed-point value */
718+
exponent += decimals;
719+
if (exponent < 0)
720+
return false; /* cannot represent values smaller than 10^-decimals */
721+
if (exponent >= 18)
722+
return false; /* cannot represent values larger than or equal to 10^(18-decimals) */
723+
724+
for (int i=0; i < exponent; ++i) {
725+
if (mantissa > (UPPER_BOUND / 10LL) || mantissa < -(UPPER_BOUND / 10LL))
726+
return false; /* overflow */
727+
mantissa *= 10;
728+
}
729+
if (mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND)
730+
return false; /* overflow */
731+
732+
if (amount_out)
733+
*amount_out = mantissa;
734+
735+
return true;
736+
}

src/utilstrencodings.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,46 @@ bool TimingResistantEqual(const T& a, const T& b)
149149
return accumulator == 0;
150150
}
151151

152+
/**
153+
* Convert from one power-of-2 number base to another.
154+
*
155+
* Examples using ConvertBits<8, 5, true>():
156+
* 000000 -> 0000000000
157+
* 202020 -> 0400100200
158+
* 757575 -> 0e151a170a
159+
* abcdef -> 150f061e1e
160+
* ffffff -> 1f1f1f1f1e
161+
*/
162+
template<int frombits, int tobits, bool pad, typename O, typename I>
163+
bool ConvertBits(const O& outfn, I it, I end) {
164+
size_t acc = 0;
165+
size_t bits = 0;
166+
constexpr size_t maxv = (1 << tobits) - 1;
167+
constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1;
168+
while (it != end) {
169+
acc = ((acc << frombits) | *it) & max_acc;
170+
bits += frombits;
171+
while (bits >= tobits) {
172+
bits -= tobits;
173+
outfn((acc >> bits) & maxv);
174+
}
175+
++it;
176+
}
177+
if (pad) {
178+
if (bits) outfn((acc << (tobits - bits)) & maxv);
179+
} else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) {
180+
return false;
181+
}
182+
return true;
183+
}
184+
185+
186+
/** Parse number as fixed point according to JSON number syntax.
187+
* See http://json.org/number.gif
188+
* @returns true on success, false on error.
189+
* @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger.
190+
*/
191+
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out);
192+
193+
152194
#endif // BITCOIN_UTILSTRENCODINGS_H

test/functional/rpc_rawtransaction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def run_test(self):
8787
#assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'})
8888
assert_raises_rpc_error(-5, "Invalid PIVX address", self.nodes[0].createrawtransaction, [], {'foo': 0})
8989
#assert_raises_rpc_error(-3, "Amount is not a number", self.nodes[0].createrawtransaction, [], {address: 'foo'})
90-
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].createrawtransaction, [], {address: -1})
90+
assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].createrawtransaction, [], {address: -1})
9191
assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)]))
9292

9393
# Test `createrawtransaction` invalid `locktime`

0 commit comments

Comments
 (0)