Detect invalid inputs during STAmount conversion (RIPD-570):

* More robust validation of input
* XRP may not be specified using fractions
* Prevent creating native amounts larger than max possible value
* Add unit tests to verify correct parsing
This commit is contained in:
Nik Bougalis
2014-09-18 01:17:32 -07:00
parent d373054fc4
commit 526ecd6a81
3 changed files with 110 additions and 111 deletions

View File

@@ -371,21 +371,27 @@ STAmount operator- (STAmount const& v1, STAmount const& v2)
//------------------------------------------------------------------------------
std::uint64_t STAmount::uRateOne =
getRate (STAmount (1), STAmount (1));
std::uint64_t const STAmount::uRateOne = getRate (STAmount (1), STAmount (1));
// Note: mIsNative and mIssue.currency must be set already!
bool
STAmount::setValue (std::string const& sAmount)
STAmount::setValue (std::string const& amount)
{
static boost::regex reNumber (
"\\`([+-]?)(\\d*)(\\.(\\d*))?([eE]([+-]?)(\\d+))?\\'");
boost::smatch smMatch;
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);
if (!boost::regex_match (sAmount, smMatch, reNumber))
boost::smatch match;
if (!boost::regex_match (amount, match, reNumber))
{
WriteLog (lsWARNING, STAmount)
<< "Number not valid: \"" << sAmount << "\"";
WriteLog (lsWARNING, STAmount) <<
"Number not valid: \"" << amount << "\"";
return false;
}
@@ -401,113 +407,56 @@ STAmount::setValue (std::string const& sAmount)
try
{
if ((smMatch[2].length () + smMatch[4].length ()) > 32)
// CHECKME: Why 32? Shouldn't this be 16?
if ((match[2].length () + match[4].length ()) > 32)
{
WriteLog (lsWARNING, STAmount) << "Overlong number: " << sAmount;
WriteLog (lsWARNING, STAmount) << "Overlong number: " << amount;
return false;
}
mIsNegative = (smMatch[1].matched && (smMatch[1] == "-"));
mIsNegative = (match[1].matched && (match[1] == "-"));
if (!smMatch[4].matched) // integer only
// Can't specify XRP using fractional representation
if (mIsNative && match[3].matched)
return false;
if (!match[4].matched) // integer only
{
mValue = beast::lexicalCast <std::uint64_t> (std::string (smMatch[2]));
mValue = beast::lexicalCastThrow <std::uint64_t> (std::string (match[2]));
mOffset = 0;
}
else
{
// integer and fraction
mValue = beast::lexicalCast <std::uint64_t> (smMatch[2] + smMatch[4]);
mOffset = - (smMatch[4].length ());
mValue = beast::lexicalCastThrow <std::uint64_t> (match[2] + match[4]);
mOffset = -(match[4].length ());
}
if (smMatch[5].matched)
if (match[5].matched)
{
// we have an exponent
if (smMatch[6].matched && (smMatch[6] == "-"))
mOffset -= beast::lexicalCast <int> (std::string (smMatch[7]));
if (match[6].matched && (match[6] == "-"))
mOffset -= beast::lexicalCastThrow <int> (std::string (match[7]));
else
mOffset += beast::lexicalCast <int> (std::string (smMatch[7]));
mOffset += beast::lexicalCastThrow <int> (std::string (match[7]));
}
canonicalize ();
WriteLog (lsTRACE, STAmount) <<
"Canonicalized \"" << amount << "\" to " << mValue << " : " << mOffset;
}
catch (...)
{
WriteLog (lsWARNING, STAmount) << "Number not parsed: \"" << sAmount << "\"";
return false;
}
WriteLog (lsTRACE, STAmount) << "Float \"" << sAmount << "\" parsed to " << mValue << " : " << mOffset;
if (mIsNative)
{
if (smMatch[3].matched)
mOffset -= SYSTEM_CURRENCY_PRECISION;
while (mOffset > 0)
{
mValue *= 10;
--mOffset;
}
while (mOffset < 0)
{
mValue /= 10;
++mOffset;
}
}
else
canonicalize ();
return true;
}
// Not meant to be the ultimate parser. For use by RPC which is supposed to be sane and trusted.
// Native has special handling:
// - Integer values are in base units.
// - Float values are in float units.
// - To avoid a mistake float value for native are specified with a "^" in place of a "."
// <-- bValid: true = valid
bool STAmount::setFullValue (std::string const& sAmount, std::string const& sCurrency, std::string const& sIssuer)
void
STAmount::setIssue (Issue const& issue)
{
//
// Figure out the currency.
//
if (!to_currency (mIssue.currency, sCurrency))
{
WriteLog (lsINFO, STAmount) << "Currency malformed: " << sCurrency;
return false;
}
mIsNative = !mIssue.currency;
//
// Figure out the issuer.
//
RippleAddress naIssuerID;
// Issuer must be "" or a valid account string.
if (!naIssuerID.setAccountID (sIssuer))
{
WriteLog (lsINFO, STAmount) << "Issuer malformed: " << sIssuer;
return false;
}
mIssue.account = naIssuerID.getAccountID ();
// Stamps not must have an issuer.
if (mIsNative && !isXRP (*this))
{
WriteLog (lsINFO, STAmount) << "Issuer specified for XRP: " << sIssuer;
return false;
}
return setValue (sAmount);
}
void STAmount::setIssue (Issue const& issue) {
mIssue = std::move(issue);
mIsNative = isXRP (*this);
}
@@ -836,7 +785,7 @@ void STAmount::canonicalize ()
--mOffset;
}
if (mValue > cMaxNative)
if (mValue > cMaxNativeN)
throw std::runtime_error ("Native currency amount out of range");
return;
@@ -870,7 +819,6 @@ void STAmount::canonicalize ()
{
mValue = 0;
mOffset = 0;
mIsNegative = false;
}
if (mOffset > cMaxOffset)

View File

@@ -65,7 +65,7 @@ public:
static const std::uint64_t cNotNative = 0x8000000000000000ull;
static const std::uint64_t cPosNative = 0x4000000000000000ull;
static std::uint64_t uRateOne;
static std::uint64_t const uRateOne;
//--------------------------------------------------------------------------
@@ -261,9 +261,6 @@ public:
// Make this private
bool setValue (std::string const& sAmount);
bool setFullValue (std::string const& sAmount,
std::string const& sCurrency = "", std::string const& sIssuer = "");
//--------------------------------------------------------------------------
//
// SerializedType

View File

@@ -111,27 +111,81 @@ public:
//--------------------------------------------------------------------------
void testSetValue (
std::string const& value, Issue const& issue, bool success = true)
{
STAmount amount (issue);
bool const result = amount.setValue (value);
expect (result == success, std::string ("parse ") + value);
if (success)
expect (amount.getText () == value, std::string ("format ") + value);
}
void testSetValue ()
{
testcase ("set value");
{
testcase ("set value (native)");
STAmount saTmp;
Issue const xrp (xrpIssue ());
#if 0
// Check native floats
saTmp.setFullValue ("1^0");
BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS == saTmp.getNValue (), "float integer failed");
saTmp.setFullValue ("0^1");
BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS / 10 == saTmp.getNValue (), "float fraction failed");
saTmp.setFullValue ("0^12");
BOOST_CHECK_MESSAGE (12 * SYSTEM_CURRENCY_PARTS / 100 == saTmp.getNValue (), "float fraction failed");
saTmp.setFullValue ("1^2");
BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS + (2 * SYSTEM_CURRENCY_PARTS / 10) == saTmp.getNValue (), "float combined failed");
#endif
// fractional XRP (i.e. drops)
testSetValue ("1", xrp);
testSetValue ("22", xrp);
testSetValue ("333", xrp);
testSetValue ("4444", xrp);
testSetValue ("55555", xrp);
testSetValue ("666666", xrp);
// Check native integer
saTmp.setFullValue ("1");
expect (1 == saTmp.getNValue (), "should be equal");
// 1 XRP up to 100 billion, in powers of 10 (in drops)
testSetValue ("1000000", xrp);
testSetValue ("10000000", xrp);
testSetValue ("100000000", xrp);
testSetValue ("1000000000", xrp);
testSetValue ("10000000000", xrp);
testSetValue ("100000000000", xrp);
testSetValue ("1000000000000", xrp);
testSetValue ("10000000000000", xrp);
testSetValue ("100000000000000", xrp);
testSetValue ("1000000000000000", xrp);
testSetValue ("10000000000000000", xrp);
testSetValue ("100000000000000000", xrp);
// Invalid native values:
testSetValue ("1.1", xrp, false);
testSetValue ("100000000000000001", xrp, false);
testSetValue ("1000000000000000000", xrp, false);
}
{
testcase ("set value (iou)");
Issue const usd (Currency (0x5553440000000000), Account (0x4985601));
testSetValue ("1", usd);
testSetValue ("10", usd);
testSetValue ("100", usd);
testSetValue ("1000", usd);
testSetValue ("10000", usd);
testSetValue ("100000", usd);
testSetValue ("1000000", usd);
testSetValue ("10000000", usd);
testSetValue ("100000000", usd);
testSetValue ("1000000000", usd);
testSetValue ("10000000000", usd);
testSetValue ("1234567.1", usd);
testSetValue ("1234567.12", usd);
testSetValue ("1234567.123", usd);
testSetValue ("1234567.1234", usd);
testSetValue ("1234567.12345", usd);
testSetValue ("1234567.123456", usd);
testSetValue ("1234567.1234567", usd);
testSetValue ("1234567.12345678", usd);
testSetValue ("1234567.123456789", usd);
}
}
//--------------------------------------------------------------------------