mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user