Files
rippled/modules/ripple_data/protocol/ripple_STAmount.cpp
2013-07-28 21:32:00 -07:00

1926 lines
59 KiB
C++

//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (STAmount)
uint64 STAmount::uRateOne = STAmount::getRate (STAmount (1), STAmount (1));
bool STAmount::issuerFromString (uint160& uDstIssuer, const std::string& sIssuer)
{
bool bSuccess = true;
if (sIssuer.size () == (160 / 4))
{
uDstIssuer.SetHex (sIssuer);
}
else
{
RippleAddress raIssuer;
if (raIssuer.setAccountID (sIssuer))
{
uDstIssuer = raIssuer.getAccountID ();
}
else
{
bSuccess = false;
}
}
return bSuccess;
}
// --> sCurrency: "", "XRP", or three letter ISO code.
bool STAmount::currencyFromString (uint160& uDstCurrency, const std::string& sCurrency)
{
bool bSuccess = true;
if (sCurrency.empty () || !sCurrency.compare (SYSTEM_CURRENCY_CODE))
{
uDstCurrency.zero ();
}
else if (3 == sCurrency.size ())
{
Blob vucIso (3);
std::transform (sCurrency.begin (), sCurrency.end (), vucIso.begin (), ::toupper);
// std::string sIso;
// sIso.assign(vucIso.begin(), vucIso.end());
// Log::out() << "currency: " << sIso;
Serializer s;
s.addZeros (96 / 8);
s.addRaw (vucIso);
s.addZeros (16 / 8);
s.addZeros (24 / 8);
s.get160 (uDstCurrency, 0);
}
else
{
bSuccess = false;
}
return bSuccess;
}
// XXX Broken for custom currencies?
std::string STAmount::getHumanCurrency () const
{
return createHumanCurrency (mCurrency);
}
bool STAmount::bSetJson (const Json::Value& jvSource)
{
try
{
STAmount saParsed (sfGeneric, jvSource);
*this = saParsed;
return true;
}
catch (const std::exception& e)
{
WriteLog (lsINFO, STAmount)
<< boost::str (boost::format ("bSetJson(): caught: %s")
% e.what ());
return false;
}
}
STAmount::STAmount (SField::ref n, const Json::Value& v)
: SerializedType (n), mValue (0), mOffset (0), mIsNegative (false)
{
Json::Value value, currency, issuer;
if (v.isObject ())
{
WriteLog (lsTRACE, STAmount)
<< boost::str (boost::format ("value='%s', currency='%s', issuer='%s'")
% v["value"].asString ()
% v["currency"].asString ()
% v["issuer"].asString ());
value = v["value"];
currency = v["currency"];
issuer = v["issuer"];
}
else if (v.isArray ())
{
value = v.get (Json::UInt (0), 0);
currency = v.get (Json::UInt (1), Json::nullValue);
issuer = v.get (Json::UInt (2), Json::nullValue);
}
else if (v.isString ())
{
std::string val = v.asString ();
std::vector<std::string> elements;
boost::split (elements, val, boost::is_any_of ("\t\n\r ,/"));
if (elements.size () > 3)
throw std::runtime_error ("invalid amount string");
value = elements[0];
if (elements.size () > 1)
currency = elements[1];
if (elements.size () > 2)
issuer = elements[2];
}
else
value = v;
mIsNative = !currency.isString () || currency.asString ().empty () || (currency.asString () == SYSTEM_CURRENCY_CODE);
if (mIsNative)
{
if (v.isObject ())
throw std::runtime_error ("XRP may not be specified as an object");
}
else
{
// non-XRP
if (!currencyFromString (mCurrency, currency.asString ()))
throw std::runtime_error ("invalid currency");
if (!issuer.isString ()
|| !issuerFromString (mIssuer, issuer.asString ()))
throw std::runtime_error ("invalid issuer");
if (mIssuer.isZero ())
throw std::runtime_error ("invalid issuer");
}
if (value.isInt ())
{
if (value.asInt () >= 0)
mValue = value.asInt ();
else
{
mValue = -value.asInt ();
mIsNegative = true;
}
canonicalize ();
}
else if (value.isUInt ())
{
mValue = v.asUInt ();
canonicalize ();
}
else if (value.isString ())
{
if (mIsNative)
{
int64 val = lexicalCastThrow <int64> (value.asString ());
if (val >= 0)
mValue = val;
else
{
mValue = -val;
mIsNegative = true;
}
canonicalize ();
}
else
{
setValue (value.asString ());
}
}
else
throw std::runtime_error ("invalid amount type");
}
std::string STAmount::createHumanCurrency (const uint160& uCurrency)
{
std::string sCurrency;
if (uCurrency.isZero ())
{
return SYSTEM_CURRENCY_CODE;
}
else if (CURRENCY_ONE == uCurrency)
{
return "1";
}
else if (CURRENCY_BAD == uCurrency)
{
return uCurrency.ToString ();
}
else
{
Serializer s (160 / 8);
s.add160 (uCurrency);
SerializerIterator sit (s);
Blob vucZeros = sit.getRaw (96 / 8);
Blob vucIso = sit.getRaw (24 / 8);
Blob vucVersion = sit.getRaw (16 / 8);
Blob vucReserved = sit.getRaw (24 / 8);
bool bIso = isZeroFilled (vucZeros.begin (), vucZeros.size ()) // Leading zeros
&& isZeroFilled (vucVersion.begin (), vucVersion.size ()) // Zero version
&& isZeroFilled (vucReserved.begin (), vucReserved.size ()); // Reserved is zero.
if (bIso)
{
sCurrency.assign (vucIso.begin (), vucIso.end ());
}
else
{
sCurrency = uCurrency.ToString ();
}
}
return sCurrency;
}
bool STAmount::setValue (const std::string& sAmount)
{
// Note: mIsNative and mCurrency must be set already!
static boost::regex reNumber ("\\`([+-]?)(\\d*)(\\.(\\d*))?([eE]([+-]?)(\\d+))?\\'");
boost::smatch smMatch;
if (!boost::regex_match (sAmount, smMatch, reNumber))
{
WriteLog (lsWARNING, STAmount) << "Number not valid: \"" << sAmount << "\"";
return false;
}
// 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
try
{
if ((smMatch[2].length () + smMatch[4].length ()) > 32)
{
WriteLog (lsWARNING, STAmount) << "Overlong number: " << sAmount;
return false;
}
mIsNegative = (smMatch[1].matched && (smMatch[1] == "-"));
if (!smMatch[4].matched) // integer only
{
mValue = lexicalCast <uint64> (std::string (smMatch[2]));
mOffset = 0;
}
else
{
// integer and fraction
mValue = lexicalCast <uint64> (smMatch[2] + smMatch[4]);
mOffset = - (smMatch[4].length ());
}
if (smMatch[5].matched)
{
// we have an exponent
if (smMatch[6].matched && (smMatch[6] == "-"))
mOffset -= lexicalCast <int> (std::string (smMatch[7]));
else
mOffset += lexicalCast <int> (std::string (smMatch[7]));
}
}
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 (const std::string& sAmount, const std::string& sCurrency, const std::string& sIssuer)
{
//
// Figure out the currency.
//
if (!currencyFromString (mCurrency, sCurrency))
{
WriteLog (lsINFO, STAmount) << "Currency malformed: " << sCurrency;
return false;
}
mIsNative = !mCurrency;
//
// 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;
}
mIssuer = naIssuerID.getAccountID ();
// Stamps not must have an issuer.
if (mIsNative && !mIssuer.isZero ())
{
WriteLog (lsINFO, STAmount) << "Issuer specified for XRP: " << sIssuer;
return false;
}
return setValue (sAmount);
}
// amount = value * [10 ^ offset]
// representation range is 10^80 - 10^(-80)
// on the wire, high 8 bits are (offset+142), low 56 bits are value
// value is zero if amount is zero, otherwise value is 10^15 to (10^16 - 1) inclusive
void STAmount::canonicalize ()
{
if (mCurrency.isZero ())
{
// native currency amounts should always have an offset of zero
mIsNative = true;
if (mValue == 0)
{
mOffset = 0;
mIsNegative = false;
return;
}
while (mOffset < 0)
{
mValue /= 10;
++mOffset;
}
while (mOffset > 0)
{
mValue *= 10;
--mOffset;
}
if (mValue > cMaxNative)
{
assert (false);
throw std::runtime_error ("Native currency amount out of range");
}
return;
}
mIsNative = false;
if (mValue == 0)
{
mOffset = -100;
mIsNegative = false;
return;
}
while ((mValue < cMinValue) && (mOffset > cMinOffset))
{
mValue *= 10;
--mOffset;
}
while (mValue > cMaxValue)
{
if (mOffset >= cMaxOffset)
throw std::runtime_error ("value overflow");
mValue /= 10;
++mOffset;
}
if ((mOffset < cMinOffset) || (mValue < cMinValue))
{
mValue = 0;
mOffset = 0;
mIsNegative = false;
}
if (mOffset > cMaxOffset)
throw std::runtime_error ("value overflow");
assert ((mValue == 0) || ((mValue >= cMinValue) && (mValue <= cMaxValue)));
assert ((mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset)));
assert ((mValue != 0) || (mOffset != -100));
}
void STAmount::add (Serializer& s) const
{
if (mIsNative)
{
assert (mOffset == 0);
if (!mIsNegative)
s.add64 (mValue | cPosNative);
else
s.add64 (mValue);
}
else
{
if (isZero ())
s.add64 (cNotNative);
else if (mIsNegative) // 512 = not native
s.add64 (mValue | (static_cast<uint64> (mOffset + 512 + 97) << (64 - 10)));
else // 256 = positive
s.add64 (mValue | (static_cast<uint64> (mOffset + 512 + 256 + 97) << (64 - 10)));
s.add160 (mCurrency);
s.add160 (mIssuer);
}
}
STAmount STAmount::createFromInt64 (SField::ref name, int64 value)
{
return value >= 0
? STAmount (name, static_cast<uint64> (value), false)
: STAmount (name, static_cast<uint64> (-value), true);
}
void STAmount::setValue (const STAmount& a)
{
mCurrency = a.mCurrency;
mIssuer = a.mIssuer;
mValue = a.mValue;
mOffset = a.mOffset;
mIsNative = a.mIsNative;
mIsNegative = a.mIsNegative;
}
int STAmount::compare (const STAmount& a) const
{
// Compares the value of a to the value of this STAmount, amounts must be comparable
if (mIsNegative != a.mIsNegative) return mIsNegative ? -1 : 1;
if (!mValue)
{
if (a.mIsNegative) return 1;
return a.mValue ? -1 : 0;
}
if (!a.mValue) return 1;
if (mOffset > a.mOffset) return mIsNegative ? -1 : 1;
if (mOffset < a.mOffset) return mIsNegative ? 1 : -1;
if (mValue > a.mValue) return mIsNegative ? -1 : 1;
if (mValue < a.mValue) return mIsNegative ? 1 : -1;
return 0;
}
STAmount* STAmount::construct (SerializerIterator& sit, SField::ref name)
{
uint64 value = sit.get64 ();
if ((value & cNotNative) == 0)
{
// native
if ((value & cPosNative) != 0)
return new STAmount (name, value & ~cPosNative, false); // positive
else if (value == 0)
throw std::runtime_error ("negative zero is not canonical");
return new STAmount (name, value, true); // negative
}
uint160 uCurrencyID = sit.get160 ();
if (!uCurrencyID)
throw std::runtime_error ("invalid non-native currency");
uint160 uIssuerID = sit.get160 ();
int offset = static_cast<int> (value >> (64 - 10)); // 10 bits for the offset, sign and "not native" flag
value &= ~ (1023ull << (64 - 10));
if (value)
{
bool isNegative = (offset & 256) == 0;
offset = (offset & 255) - 97; // center the range
if ((value < cMinValue) || (value > cMaxValue) || (offset < cMinOffset) || (offset > cMaxOffset))
throw std::runtime_error ("invalid currency value");
return new STAmount (name, uCurrencyID, uIssuerID, value, offset, isNegative);
}
if (offset != 512)
throw std::runtime_error ("invalid currency value");
return new STAmount (name, uCurrencyID, uIssuerID);
}
int64 STAmount::getSNValue () const
{
// signed native value
if (!mIsNative) throw std::runtime_error ("not native");
if (mIsNegative) return - static_cast<int64> (mValue);
return static_cast<int64> (mValue);
}
void STAmount::setSNValue (int64 v)
{
if (!mIsNative) throw std::runtime_error ("not native");
if (v > 0)
{
mIsNegative = false;
mValue = static_cast<uint64> (v);
}
else
{
mIsNegative = true;
mValue = static_cast<uint64> (-v);
}
}
std::string STAmount::getRaw () const
{
// show raw internal form
if (mValue == 0) return "0";
if (mIsNative)
{
if (mIsNegative) return std::string ("-") + lexicalCast <std::string> (mValue);
else return lexicalCast <std::string> (mValue);
}
if (mIsNegative)
return mCurrency.GetHex () + ": -" +
lexicalCast <std::string> (mValue) + "e" + lexicalCast <std::string> (mOffset);
else return mCurrency.GetHex () + ": " +
lexicalCast <std::string> (mValue) + "e" + lexicalCast <std::string> (mOffset);
}
std::string STAmount::getText () const
{
// keep full internal accuracy, but make more human friendly if posible
if (isZero ()) return "0";
if (mIsNative)
{
if (mIsNegative)
return std::string ("-") + lexicalCast <std::string> (mValue);
else return lexicalCast <std::string> (mValue);
}
if ((mOffset != 0) && ((mOffset < -25) || (mOffset > -5)))
{
if (mIsNegative)
return std::string ("-") + lexicalCast <std::string> (mValue) +
"e" + lexicalCast <std::string> (mOffset);
else
return lexicalCast <std::string> (mValue) + "e" + lexicalCast <std::string> (mOffset);
}
std::string val = "000000000000000000000000000";
val += lexicalCast <std::string> (mValue);
val += "00000000000000000000000";
std::string pre = val.substr (0, mOffset + 43);
std::string post = val.substr (mOffset + 43);
size_t s_pre = pre.find_first_not_of ('0');
if (s_pre == std::string::npos)
pre = "0";
else
pre = pre.substr (s_pre);
size_t s_post = post.find_last_not_of ('0');
if (mIsNegative) pre = std::string ("-") + pre;
if (s_post == std::string::npos)
return pre;
else
return pre + "." + post.substr (0, s_post + 1);
}
bool STAmount::isComparable (const STAmount& t) const
{
// are these two STAmount instances in the same currency
if (mIsNative) return t.mIsNative;
if (t.mIsNative) return false;
return mCurrency == t.mCurrency;
}
bool STAmount::isEquivalent (const SerializedType& t) const
{
const STAmount* v = dynamic_cast<const STAmount*> (&t);
if (!v) return false;
return isComparable (*v) && (mIsNegative == v->mIsNegative) && (mValue == v->mValue) && (mOffset == v->mOffset);
}
void STAmount::throwComparable (const STAmount& t) const
{
// throw an exception if these two STAmount instances are incomparable
if (!isComparable (t))
throw std::runtime_error ("amounts are not comparable");
}
bool STAmount::operator== (const STAmount& a) const
{
return isComparable (a) && (mIsNegative == a.mIsNegative) && (mOffset == a.mOffset) && (mValue == a.mValue);
}
bool STAmount::operator!= (const STAmount& a) const
{
return (mOffset != a.mOffset) || (mValue != a.mValue) || (mIsNegative != a.mIsNegative) || !isComparable (a);
}
bool STAmount::operator< (const STAmount& a) const
{
throwComparable (a);
return compare (a) < 0;
}
bool STAmount::operator> (const STAmount& a) const
{
throwComparable (a);
return compare (a) > 0;
}
bool STAmount::operator<= (const STAmount& a) const
{
throwComparable (a);
return compare (a) <= 0;
}
bool STAmount::operator>= (const STAmount& a) const
{
throwComparable (a);
return compare (a) >= 0;
}
STAmount& STAmount::operator+= (const STAmount& a)
{
*this = *this + a;
return *this;
}
STAmount& STAmount::operator-= (const STAmount& a)
{
*this = *this - a;
return *this;
}
STAmount STAmount::operator- (void) const
{
if (mValue == 0) return *this;
return STAmount (getFName (), mCurrency, mIssuer, mValue, mOffset, mIsNative, !mIsNegative);
}
STAmount& STAmount::operator= (uint64 v)
{
// does not copy name, does not change currency type
mOffset = 0;
mValue = v;
mIsNegative = false;
if (!mIsNative) canonicalize ();
return *this;
}
STAmount& STAmount::operator+= (uint64 v)
{
if (mIsNative)
setSNValue (getSNValue () + static_cast<int64> (v));
else *this += STAmount (mCurrency, v);
return *this;
}
STAmount& STAmount::operator-= (uint64 v)
{
if (mIsNative)
setSNValue (getSNValue () - static_cast<int64> (v));
else *this -= STAmount (mCurrency, v);
return *this;
}
bool STAmount::operator< (uint64 v) const
{
return getSNValue () < static_cast<int64> (v);
}
bool STAmount::operator> (uint64 v) const
{
return getSNValue () > static_cast<int64> (v);
}
bool STAmount::operator<= (uint64 v) const
{
return getSNValue () <= static_cast<int64> (v);
}
bool STAmount::operator>= (uint64 v) const
{
return getSNValue () >= static_cast<int64> (v);
}
STAmount STAmount::operator+ (uint64 v) const
{
return STAmount (getFName (), getSNValue () + static_cast<int64> (v));
}
STAmount STAmount::operator- (uint64 v) const
{
return STAmount (getFName (), getSNValue () - static_cast<int64> (v));
}
STAmount::operator double () const
{
// Does not keep the precise value. Not recommended
if (!mValue)
return 0.0;
if (mIsNegative) return -1.0 * static_cast<double> (mValue) * pow (10.0, mOffset);
return static_cast<double> (mValue) * pow (10.0, mOffset);
}
STAmount operator+ (const STAmount& v1, const STAmount& v2)
{
v1.throwComparable (v2);
if (v2.isZero ()) return v1;
if (v1.isZero ())
{
// Result must be in terms of v1 currency and issuer.
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, v2.mValue, v2.mOffset, v2.mIsNegative);
}
if (v1.mIsNative)
return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ());
int ov1 = v1.mOffset, ov2 = v2.mOffset;
int64 vv1 = static_cast<int64> (v1.mValue), vv2 = static_cast<int64> (v2.mValue);
if (v1.mIsNegative) vv1 = -vv1;
if (v2.mIsNegative) vv2 = -vv2;
while (ov1 < ov2)
{
vv1 /= 10;
++ov1;
}
while (ov2 < ov1)
{
vv2 /= 10;
++ov2;
}
// this addition cannot overflow an int64, it can overflow an STAmount and the constructor will throw
int64 fv = vv1 + vv2;
if ((fv >= -10) && (fv <= 10))
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer);
else if (fv >= 0)
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, fv, ov1, false);
else
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true);
}
STAmount operator- (const STAmount& v1, const STAmount& v2)
{
v1.throwComparable (v2);
if (v2.isZero ()) return v1;
if (v2.mIsNative)
{
// XXX This could be better, check for overflow and that maximum range is covered.
return STAmount::createFromInt64 (v1.getFName (), v1.getSNValue () - v2.getSNValue ());
}
int ov1 = v1.mOffset, ov2 = v2.mOffset;
int64 vv1 = static_cast<int64> (v1.mValue), vv2 = static_cast<int64> (v2.mValue);
if (v1.mIsNegative) vv1 = -vv1;
if (v2.mIsNegative) vv2 = -vv2;
while (ov1 < ov2)
{
vv1 /= 10;
++ov1;
}
while (ov2 < ov1)
{
vv2 /= 10;
++ov2;
}
// this subtraction cannot overflow an int64, it can overflow an STAmount and the constructor will throw
int64 fv = vv1 - vv2;
if ((fv >= -10) && (fv <= 10))
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer);
else if (fv >= 0)
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, fv, ov1, false);
else
return STAmount (v1.getFName (), v1.mCurrency, v1.mIssuer, -fv, ov1, true);
}
STAmount STAmount::divide (const STAmount& num, const STAmount& den, const uint160& uCurrencyID, const uint160& uIssuerID)
{
if (den.isZero ())
throw std::runtime_error ("division by zero");
if (num.isZero ())
return STAmount (uCurrencyID, uIssuerID);
uint64 numVal = num.mValue, denVal = den.mValue;
int numOffset = num.mOffset, denOffset = den.mOffset;
if (num.mIsNative)
while (numVal < STAmount::cMinValue)
{
// Need to bring into range
numVal *= 10;
--numOffset;
}
if (den.mIsNative)
while (denVal < STAmount::cMinValue)
{
denVal *= 10;
--denOffset;
}
// Compute (numerator * 10^17) / denominator
CBigNum v;
if ((BN_add_word64 (&v, numVal) != 1) ||
(BN_mul_word64 (&v, tenTo17) != 1) ||
(BN_div_word64 (&v, denVal) == ((uint64) - 1)))
{
throw std::runtime_error ("internal bn error");
}
// 10^16 <= quotient <= 10^18
assert (BN_num_bytes (&v) <= 64);
return STAmount (uCurrencyID, uIssuerID, v.getuint64 () + 5,
numOffset - denOffset - 17, num.mIsNegative != den.mIsNegative);
}
STAmount STAmount::multiply (const STAmount& v1, const STAmount& v2, const uint160& uCurrencyID, const uint160& uIssuerID)
{
if (v1.isZero () || v2.isZero ())
return STAmount (uCurrencyID, uIssuerID);
if (v1.mIsNative && v2.mIsNative && uCurrencyID.isZero ())
{
uint64 minV = (v1.getSNValue () < v2.getSNValue ()) ? v1.getSNValue () : v2.getSNValue ();
uint64 maxV = (v1.getSNValue () < v2.getSNValue ()) ? v2.getSNValue () : v1.getSNValue ();
if (minV > 3000000000ull) // sqrt(cMaxNative)
throw std::runtime_error ("Native value overflow");
if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32
throw std::runtime_error ("Native value overflow");
return STAmount (v1.getFName (), minV * maxV);
}
uint64 value1 = v1.mValue, value2 = v2.mValue;
int offset1 = v1.mOffset, offset2 = v2.mOffset;
if (v1.mIsNative)
{
while (value1 < STAmount::cMinValue)
{
value1 *= 10;
--offset1;
}
}
if (v2.mIsNative)
{
while (value2 < STAmount::cMinValue)
{
value2 *= 10;
--offset2;
}
}
// Compute (numerator * denominator) / 10^14 with rounding
// 10^16 <= result <= 10^18
CBigNum v;
if ((BN_add_word64 (&v, value1) != 1) ||
(BN_mul_word64 (&v, value2) != 1) ||
(BN_div_word64 (&v, tenTo14) == ((uint64) - 1)))
{
throw std::runtime_error ("internal bn error");
}
// 10^16 <= product <= 10^18
assert (BN_num_bytes (&v) <= 64);
return STAmount (uCurrencyID, uIssuerID, v.getuint64 () + 7, offset1 + offset2 + 14,
v1.mIsNegative != v2.mIsNegative);
}
// Convert an offer into an index amount so they sort by rate.
// A taker will take the best, lowest, rate first.
// (e.g. a taker will prefer pay 1 get 3 over pay 1 get 2.
// --> offerOut: takerGets: How much the offerer is selling to the taker.
// --> offerIn: takerPays: How much the offerer is receiving from the taker.
// <-- uRate: normalize(offerIn/offerOut)
// A lower rate is better for the person taking the order.
// The taker gets more for less with a lower rate.
// Zero is returned if the offer is worthless.
uint64 STAmount::getRate (const STAmount& offerOut, const STAmount& offerIn)
{
if (offerOut.isZero ())
return 0;
try
{
STAmount r = divide (offerIn, offerOut, CURRENCY_ONE, ACCOUNT_ONE);
if (r.isZero ()) // offer is too good
return 0;
assert ((r.getExponent () >= -100) && (r.getExponent () <= 155));
uint64 ret = r.getExponent () + 100;
return (ret << (64 - 8)) | r.getMantissa ();
}
catch (...)
{
// overflow -- very bad offer
return 0;
}
}
STAmount STAmount::setRate (uint64 rate)
{
if (rate == 0)
return STAmount (CURRENCY_ONE, ACCOUNT_ONE);
uint64 mantissa = rate & ~ (255ull << (64 - 8));
int exponent = static_cast<int> (rate >> (64 - 8)) - 100;
return STAmount (CURRENCY_ONE, ACCOUNT_ONE, mantissa, exponent);
}
// Existing offer is on the books.
// Price is offer owner's, which might be better for taker.
// Taker pays what they can.
// Taker gets all taker can pay for with saTakerFunds/uTakerPaysRate, limited by saOfferPays and saOfferFunds/uOfferPaysRate.
// If taker is an offer, taker is spending at same or better rate than they wanted.
// Taker should consider themselves as wanting to buy X amount.
// Taker is willing to pay at most the rate of Y/X each.
// Buy semantics:
// - After having some part of their offer fulfilled at a better rate their offer should be reduced accordingly.
//
// There are no quality costs for offer vs offer taking.
//
// --> bSell: True for sell semantics.
// --> uTakerPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by taker.
// --> uOfferPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by offer owner.
// --> saOfferRate: Original saOfferGets/saOfferPays, when offer was made.
// --> saOfferFunds: Limit for saOfferPays : How much can pay including fees.
// --> saTakerFunds: Limit for saOfferGets : How much can pay including fees.
// --> saOfferPays: Request : this should be reduced as the offer is fullfilled.
// --> saOfferGets: Request : this should be reduced as the offer is fullfilled.
// --> saTakerPays: Limit for taker to pay.
// --> saTakerGets: Limit for taker to get.
// <-- saTakerPaid: Actual
// <-- saTakerGot: Actual
// <-- saTakerIssuerFee: Actual
// <-- saOfferIssuerFee: Actual
bool STAmount::applyOffer (
const bool bSell,
const uint32 uTakerPaysRate, const uint32 uOfferPaysRate,
const STAmount& saOfferRate,
const STAmount& saOfferFunds, const STAmount& saTakerFunds,
const STAmount& saOfferPays, const STAmount& saOfferGets,
const STAmount& saTakerPays, const STAmount& saTakerGets,
STAmount& saTakerPaid, STAmount& saTakerGot,
STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee)
{
saOfferGets.throwComparable (saTakerFunds);
assert (saOfferFunds.isPositive () && saTakerFunds.isPositive ()); // Both must have funds.
assert (saOfferGets.isPositive () && saOfferPays.isPositive ()); // Must not be a null offer.
// Available = limited by funds.
// Limit offerer funds available, by transfer fees.
STAmount saOfferFundsAvailable = QUALITY_ONE == uOfferPaysRate
? saOfferFunds // As is.
: STAmount::divide (saOfferFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9)); // Reduce by offer fees.
WriteLog (lsINFO, STAmount) << "applyOffer: uOfferPaysRate=" << uOfferPaysRate;
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferFundsAvailable=" << saOfferFundsAvailable.getFullText ();
// Limit taker funds available, by transfer fees.
STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate
? saTakerFunds // As is.
: STAmount::divide (saTakerFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9)); // Reduce by taker fees.
WriteLog (lsINFO, STAmount) << "applyOffer: TAKER_FEES=" << STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9).getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: uTakerPaysRate=" << uTakerPaysRate;
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFundsAvailable=" << saTakerFundsAvailable.getFullText ();
STAmount saOfferPaysAvailable; // Amount offer can pay out, limited by offer and offerer funds.
STAmount saOfferGetsAvailable; // Amount offer would get, limited by offer funds.
if (saOfferFundsAvailable >= saOfferPays)
{
// Offer was fully funded, avoid math shenanigans.
saOfferPaysAvailable = saOfferPays;
saOfferGetsAvailable = saOfferGets;
}
else
{
// Offer has limited funding, limit offer gets and pays by funds available.
saOfferPaysAvailable = saOfferFundsAvailable;
saOfferGetsAvailable = std::min (saOfferGets, mulRound (saOfferPaysAvailable, saOfferRate, saOfferGets, true));
}
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferPaysAvailable=" << saOfferFundsAvailable.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferGetsAvailable=" << saOfferGetsAvailable.getFullText ();
STAmount saTakerPaysAvailable = std::min (saTakerPays, saTakerFundsAvailable);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysAvailable=" << saTakerPaysAvailable.getFullText ();
// Limited = limited by other sides raw numbers.
// Taker can't pay more to offer than offer can get.
STAmount saTakerPaysLimited = std::min (saTakerPaysAvailable, saOfferGetsAvailable);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysLimited=" << saTakerPaysLimited.getFullText ();
// Align saTakerGetsLimited with saTakerPaysLimited.
STAmount saTakerGetsLimited = saTakerPaysLimited >= saOfferGetsAvailable
? saOfferPaysAvailable // Potentially take entire offer. Avoid math shenanigans.
: std::min (saOfferPaysAvailable, divRound (saTakerPaysLimited, saOfferRate, saTakerGets, true)); // Taker a portion of offer.
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferRate=" << saOfferRate.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGetsLimited=" << saTakerGetsLimited.getFullText ();
// Got & Paid = Calculated by price and transfered without fees.
// Compute from got as when !bSell, we want got to be exact to finish off offer if possible.
saTakerGot = bSell
? saTakerGetsLimited // Get all available that are paid for.
: std::min (saTakerGets, saTakerGetsLimited); // Limit by wanted.
saTakerPaid = saTakerGot >= saOfferPaysAvailable
? saOfferGetsAvailable
: std::min (saOfferGetsAvailable, mulRound (saTakerGot, saOfferRate, saTakerFunds, true));
saTakerPaid = std::min (saTakerPaid, saTakerPaysAvailable); // Due to rounding must clamp.
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaid=" << saTakerPaid.getFullText ();
if (uTakerPaysRate == QUALITY_ONE)
{
saTakerIssuerFee = STAmount (saTakerPaid.getCurrency (), saTakerPaid.getIssuer ());
}
else
{
// Compute fees in a rounding safe way.
STAmount saTransferRate = STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9);
WriteLog (lsINFO, STAmount) << "applyOffer: saTransferRate=" << saTransferRate.getFullText ();
// TakerCost includes transfer fees.
STAmount saTakerCost = STAmount::mulRound (saTakerPaid, saTransferRate, true);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerCost=" << saTakerCost.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFunds=" << saTakerFunds.getFullText ();
saTakerIssuerFee = saTakerCost > saTakerFunds
? saTakerFunds - saTakerPaid // Not enough funds to cover fee, stiff issuer the rounding error.
: saTakerCost - saTakerPaid;
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerIssuerFee=" << saTakerIssuerFee.getFullText ();
assert (!saTakerIssuerFee.isNegative ());
}
if (uOfferPaysRate == QUALITY_ONE)
{
saOfferIssuerFee = STAmount (saTakerGot.getCurrency (), saTakerGot.getIssuer ());
}
else
{
// Compute fees in a rounding safe way.
STAmount saOfferCost = STAmount::mulRound (saTakerGot, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9), true);
saOfferIssuerFee = saOfferCost > saOfferFunds
? saOfferFunds - saTakerGot // Not enough funds to cover fee, stiff issuer the rounding error.
: saOfferCost - saTakerGot;
}
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();
return saTakerGot >= saOfferPaysAvailable; // True, if consumed offer.
}
STAmount STAmount::getPay (const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed)
{
// Someone wants to get (needed) out of the offer, how much should they pay in?
if (offerOut.isZero ())
return STAmount (offerIn.getCurrency (), offerIn.getIssuer ());
if (needed >= offerOut)
{
// They need more than offered, pay full amount.
return needed;
}
STAmount ret = divide (multiply (needed, offerIn, CURRENCY_ONE, ACCOUNT_ONE), offerOut, offerIn.getCurrency (), offerIn.getIssuer ());
return (ret > offerIn) ? offerIn : ret;
}
STAmount STAmount::deserialize (SerializerIterator& it)
{
UPTR_T<STAmount> s (dynamic_cast<STAmount*> (construct (it, sfGeneric)));
STAmount ret (*s);
return ret;
}
std::string STAmount::getFullText () const
{
static const boost::format nativeFormat ("%s/" SYSTEM_CURRENCY_CODE);
static const boost::format noIssuer ("%s/%s/0");
static const boost::format issuerOne ("%s/%s/1");
static const boost::format normal ("%s/%s/%s");
if (mIsNative)
{
return str (boost::format (nativeFormat) % getText ());
}
else if (!mIssuer)
{
return str (boost::format (noIssuer) % getText () % getHumanCurrency ());
}
else if (mIssuer == ACCOUNT_ONE)
{
return str (boost::format (issuerOne) % getText () % getHumanCurrency ());
}
else
{
return str (boost::format (normal)
% getText ()
% getHumanCurrency ()
% RippleAddress::createHumanAccountID (mIssuer));
}
}
STAmount STAmount::getRound () const
{
if (mIsNative)
return *this;
uint64 valueDigits = mValue % 1000000000ull;
if (valueDigits == 1)
return STAmount (mCurrency, mIssuer, mValue - 1, mOffset, mIsNegative);
else if (valueDigits == 999999999ull)
return STAmount (mCurrency, mIssuer, mValue + 1, mOffset, mIsNegative);
return *this;
}
void STAmount::roundSelf ()
{
if (mIsNative)
return;
uint64 valueDigits = mValue % 1000000000ull;
if (valueDigits == 1)
{
mValue -= 1;
if (mValue < cMinValue)
canonicalize ();
}
else if (valueDigits == 999999999ull)
{
mValue += 1;
if (mValue > cMaxValue)
canonicalize ();
}
}
void STAmount::setJson (Json::Value& elem) const
{
elem = Json::objectValue;
if (!mIsNative)
{
// It is an error for currency or issuer not to be specified for valid json.
elem["value"] = getText ();
elem["currency"] = getHumanCurrency ();
elem["issuer"] = RippleAddress::createHumanAccountID (mIssuer);
}
else
{
elem = getText ();
}
}
Json::Value STAmount::getJson (int) const
{
Json::Value elem;
setJson (elem);
return elem;
}
//------------------------------------------------------------------------------
class STAmountTests : public UnitTest
{
public:
STAmountTests () : UnitTest ("STAmount", "ripple")
{
}
static STAmount serializeAndDeserialize (const STAmount& s)
{
Serializer ser;
s.add (ser);
SerializerIterator sit (ser);
return STAmount::deserialize (sit);
}
//--------------------------------------------------------------------------
bool roundTest (int n, int d, int m)
{
// check STAmount rounding
STAmount num (CURRENCY_ONE, ACCOUNT_ONE, n);
STAmount den (CURRENCY_ONE, ACCOUNT_ONE, d);
STAmount mul (CURRENCY_ONE, ACCOUNT_ONE, m);
STAmount quot = STAmount::divide (n, d, CURRENCY_ONE, ACCOUNT_ONE);
STAmount res = STAmount::multiply (quot, mul, CURRENCY_ONE, ACCOUNT_ONE);
expect (! res.isNative (), "Product should not be native");
res.roundSelf ();
STAmount cmp (CURRENCY_ONE, ACCOUNT_ONE, (n * m) / d);
expect (! cmp.isNative (), "Comparison amount should not be native");
if (res != cmp)
{
cmp.throwComparable (res);
WriteLog (lsWARNING, STAmount) << "(" << num.getText () << "/" << den.getText () << ") X " << mul.getText () << " = "
<< res.getText () << " not " << cmp.getText ();
fail ("Rounding");
return false;
}
else
{
pass ();
}
return true;
}
void mulTest (int a, int b)
{
STAmount aa (CURRENCY_ONE, ACCOUNT_ONE, a);
STAmount bb (CURRENCY_ONE, ACCOUNT_ONE, b);
STAmount prod1 (STAmount::multiply (aa, bb, CURRENCY_ONE, ACCOUNT_ONE));
expect (! prod1.isNative ());
STAmount prod2 (CURRENCY_ONE, ACCOUNT_ONE, static_cast<uint64> (a) * static_cast<uint64> (b));
if (prod1 != prod2)
{
WriteLog (lsWARNING, STAmount) << "nn(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
<< " not " << prod2.getFullText ();
fail ("Multiplication result is not exact");
}
else
{
pass ();
}
aa = a;
prod1 = STAmount::multiply (aa, bb, CURRENCY_ONE, ACCOUNT_ONE);
if (prod1 != prod2)
{
WriteLog (lsWARNING, STAmount) << "n(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText ()
<< " not " << prod2.getFullText ();
fail ("Multiplication result is not exact");
}
else
{
pass ();
}
}
//--------------------------------------------------------------------------
void testSetValue ()
{
beginTestCase ("set value");
STAmount saTmp;
#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
// Check native integer
saTmp.setFullValue ("1");
expect (1 == saTmp.getNValue (), "should be equal");
}
//--------------------------------------------------------------------------
void testNativeCurrency ()
{
beginTestCase ("native currency");
STAmount zero, one (1), hundred (100);
unexpected (serializeAndDeserialize (zero) != zero, "STAmount fail");
unexpected (serializeAndDeserialize (one) != one, "STAmount fail");
unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");
unexpected (!zero.isNative (), "STAmount fail");
unexpected (!hundred.isNative (), "STAmount fail");
unexpected (!zero.isZero (), "STAmount fail");
unexpected (one.isZero (), "STAmount fail");
unexpected (hundred.isZero (), "STAmount fail");
unexpected ((zero < zero), "STAmount fail");
unexpected (! (zero < one), "STAmount fail");
unexpected (! (zero < hundred), "STAmount fail");
unexpected ((one < zero), "STAmount fail");
unexpected ((one < one), "STAmount fail");
unexpected (! (one < hundred), "STAmount fail");
unexpected ((hundred < zero), "STAmount fail");
unexpected ((hundred < one), "STAmount fail");
unexpected ((hundred < hundred), "STAmount fail");
unexpected ((zero > zero), "STAmount fail");
unexpected ((zero > one), "STAmount fail");
unexpected ((zero > hundred), "STAmount fail");
unexpected (! (one > zero), "STAmount fail");
unexpected ((one > one), "STAmount fail");
unexpected ((one > hundred), "STAmount fail");
unexpected (! (hundred > zero), "STAmount fail");
unexpected (! (hundred > one), "STAmount fail");
unexpected ((hundred > hundred), "STAmount fail");
unexpected (! (zero <= zero), "STAmount fail");
unexpected (! (zero <= one), "STAmount fail");
unexpected (! (zero <= hundred), "STAmount fail");
unexpected ((one <= zero), "STAmount fail");
unexpected (! (one <= one), "STAmount fail");
unexpected (! (one <= hundred), "STAmount fail");
unexpected ((hundred <= zero), "STAmount fail");
unexpected ((hundred <= one), "STAmount fail");
unexpected (! (hundred <= hundred), "STAmount fail");
unexpected (! (zero >= zero), "STAmount fail");
unexpected ((zero >= one), "STAmount fail");
unexpected ((zero >= hundred), "STAmount fail");
unexpected (! (one >= zero), "STAmount fail");
unexpected (! (one >= one), "STAmount fail");
unexpected ((one >= hundred), "STAmount fail");
unexpected (! (hundred >= zero), "STAmount fail");
unexpected (! (hundred >= one), "STAmount fail");
unexpected (! (hundred >= hundred), "STAmount fail");
unexpected (! (zero == zero), "STAmount fail");
unexpected ((zero == one), "STAmount fail");
unexpected ((zero == hundred), "STAmount fail");
unexpected ((one == zero), "STAmount fail");
unexpected (! (one == one), "STAmount fail");
unexpected ((one == hundred), "STAmount fail");
unexpected ((hundred == zero), "STAmount fail");
unexpected ((hundred == one), "STAmount fail");
unexpected (! (hundred == hundred), "STAmount fail");
unexpected ((zero != zero), "STAmount fail");
unexpected (! (zero != one), "STAmount fail");
unexpected (! (zero != hundred), "STAmount fail");
unexpected (! (one != zero), "STAmount fail");
unexpected ((one != one), "STAmount fail");
unexpected (! (one != hundred), "STAmount fail");
unexpected (! (hundred != zero), "STAmount fail");
unexpected (! (hundred != one), "STAmount fail");
unexpected ((hundred != hundred), "STAmount fail");
unexpected (STAmount ().getText () != "0", "STAmount fail");
unexpected (STAmount (31).getText () != "31", "STAmount fail");
unexpected (STAmount (310).getText () != "310", "STAmount fail");
}
//--------------------------------------------------------------------------
void testCustomCurrency ()
{
beginTestCase ("custom currency");
STAmount zero (CURRENCY_ONE, ACCOUNT_ONE), one (CURRENCY_ONE, ACCOUNT_ONE, 1), hundred (CURRENCY_ONE, ACCOUNT_ONE, 100);
serializeAndDeserialize (one).getRaw ();
unexpected (serializeAndDeserialize (zero) != zero, "STAmount fail");
unexpected (serializeAndDeserialize (one) != one, "STAmount fail");
unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail");
unexpected (zero.isNative (), "STAmount fail");
unexpected (hundred.isNative (), "STAmount fail");
unexpected (!zero.isZero (), "STAmount fail");
unexpected (one.isZero (), "STAmount fail");
unexpected (hundred.isZero (), "STAmount fail");
unexpected ((zero < zero), "STAmount fail");
unexpected (! (zero < one), "STAmount fail");
unexpected (! (zero < hundred), "STAmount fail");
unexpected ((one < zero), "STAmount fail");
unexpected ((one < one), "STAmount fail");
unexpected (! (one < hundred), "STAmount fail");
unexpected ((hundred < zero), "STAmount fail");
unexpected ((hundred < one), "STAmount fail");
unexpected ((hundred < hundred), "STAmount fail");
unexpected ((zero > zero), "STAmount fail");
unexpected ((zero > one), "STAmount fail");
unexpected ((zero > hundred), "STAmount fail");
unexpected (! (one > zero), "STAmount fail");
unexpected ((one > one), "STAmount fail");
unexpected ((one > hundred), "STAmount fail");
unexpected (! (hundred > zero), "STAmount fail");
unexpected (! (hundred > one), "STAmount fail");
unexpected ((hundred > hundred), "STAmount fail");
unexpected (! (zero <= zero), "STAmount fail");
unexpected (! (zero <= one), "STAmount fail");
unexpected (! (zero <= hundred), "STAmount fail");
unexpected ((one <= zero), "STAmount fail");
unexpected (! (one <= one), "STAmount fail");
unexpected (! (one <= hundred), "STAmount fail");
unexpected ((hundred <= zero), "STAmount fail");
unexpected ((hundred <= one), "STAmount fail");
unexpected (! (hundred <= hundred), "STAmount fail");
unexpected (! (zero >= zero), "STAmount fail");
unexpected ((zero >= one), "STAmount fail");
unexpected ((zero >= hundred), "STAmount fail");
unexpected (! (one >= zero), "STAmount fail");
unexpected (! (one >= one), "STAmount fail");
unexpected ((one >= hundred), "STAmount fail");
unexpected (! (hundred >= zero), "STAmount fail");
unexpected (! (hundred >= one), "STAmount fail");
unexpected (! (hundred >= hundred), "STAmount fail");
unexpected (! (zero == zero), "STAmount fail");
unexpected ((zero == one), "STAmount fail");
unexpected ((zero == hundred), "STAmount fail");
unexpected ((one == zero), "STAmount fail");
unexpected (! (one == one), "STAmount fail");
unexpected ((one == hundred), "STAmount fail");
unexpected ((hundred == zero), "STAmount fail");
unexpected ((hundred == one), "STAmount fail");
unexpected (! (hundred == hundred), "STAmount fail");
unexpected ((zero != zero), "STAmount fail");
unexpected (! (zero != one), "STAmount fail");
unexpected (! (zero != hundred), "STAmount fail");
unexpected (! (one != zero), "STAmount fail");
unexpected ((one != one), "STAmount fail");
unexpected (! (one != hundred), "STAmount fail");
unexpected (! (hundred != zero), "STAmount fail");
unexpected (! (hundred != one), "STAmount fail");
unexpected ((hundred != hundred), "STAmount fail");
unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE).getText () != "0", "STAmount fail");
unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31).getText () != "31", "STAmount fail");
unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31, 1).getText () != "310", "STAmount fail");
unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31, -1).getText () != "3.1", "STAmount fail");
unexpected (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 31, -2).getText () != "0.31", "STAmount fail");
unexpected (STAmount::multiply (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "60",
"STAmount multiply fail 1");
unexpected (STAmount::multiply (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount (3), uint160 (), ACCOUNT_XRP).getText () != "60",
"STAmount multiply fail 2");
unexpected (STAmount::multiply (STAmount (20), STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "60",
"STAmount multiply fail 3");
unexpected (STAmount::multiply (STAmount (20), STAmount (3), uint160 (), ACCOUNT_XRP).getText () != "60",
"STAmount multiply fail 4");
if (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "20")
{
WriteLog (lsFATAL, STAmount) << "60/3 = " <<
STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60),
STAmount (3), CURRENCY_ONE, ACCOUNT_ONE).getText ();
fail ("STAmount divide fail");
}
else
{
pass ();
}
unexpected (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (3), uint160 (), ACCOUNT_XRP).getText () != "20",
"STAmount divide fail");
unexpected (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 3), CURRENCY_ONE, ACCOUNT_ONE).getText () != "20",
"STAmount divide fail");
unexpected (STAmount::divide (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 3), uint160 (), ACCOUNT_XRP).getText () != "20",
"STAmount divide fail");
STAmount a1 (CURRENCY_ONE, ACCOUNT_ONE, 60), a2 (CURRENCY_ONE, ACCOUNT_ONE, 10, -1);
unexpected (STAmount::divide (a2, a1, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate (STAmount::getRate (a1, a2)),
"STAmount setRate(getRate) fail");
unexpected (STAmount::divide (a1, a2, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate (STAmount::getRate (a2, a1)),
"STAmount setRate(getRate) fail");
}
//--------------------------------------------------------------------------
void testArithmetic ()
{
beginTestCase ("arithmetic");
CBigNum b;
for (int i = 0; i < 16; ++i)
{
uint64 r = rand ();
r <<= 32;
r |= rand ();
b.setuint64 (r);
if (b.getuint64 () != r)
{
WriteLog (lsFATAL, STAmount) << r << " != " << b.getuint64 () << " " << b.ToString (16);
fail ("setull64/getull64 failure");
}
else
{
pass ();
}
}
// Test currency multiplication and division operations such as
// convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded
unexpected (STAmount::getRate (STAmount (1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 1");
unexpected (STAmount::getRate (STAmount (10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 2");
unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 3");
unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 4");
unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 5");
unexpected (STAmount::getRate (STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 6");
unexpected (STAmount::getRate (STAmount (1), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 7");
unexpected (STAmount::getRate (STAmount (10), STAmount (CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull),
"STAmount getRate fail 8");
roundTest (1, 3, 3);
roundTest (2, 3, 9);
roundTest (1, 7, 21);
roundTest (1, 2, 4);
roundTest (3, 9, 18);
roundTest (7, 11, 44);
for (int i = 0; i <= 100000; ++i)
mulTest (rand () % 10000000, rand () % 10000000);
}
//--------------------------------------------------------------------------
void testUnderflow ()
{
beginTestCase ("underflow");
STAmount bigNative (STAmount::cMaxNative / 2);
STAmount bigValue (CURRENCY_ONE, ACCOUNT_ONE,
(STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMaxOffset - 1);
STAmount smallValue (CURRENCY_ONE, ACCOUNT_ONE,
(STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMinOffset + 1);
STAmount zero (CURRENCY_ONE, ACCOUNT_ONE, 0);
STAmount smallXsmall = STAmount::multiply (smallValue, smallValue, CURRENCY_ONE, ACCOUNT_ONE);
expect (smallXsmall.isZero (), "smallXsmall != 0");
STAmount bigDsmall = STAmount::divide (smallValue, bigValue, CURRENCY_ONE, ACCOUNT_ONE);
expect (bigDsmall.isZero (), String ("small/big != 0: ") + bigDsmall.getText ());
bigDsmall = STAmount::divide (smallValue, bigNative, CURRENCY_ONE, uint160 ());
expect (bigDsmall.isZero (), String ("small/bigNative != 0: ") + bigDsmall.getText ());
bigDsmall = STAmount::divide (smallValue, bigValue, uint160 (), uint160 ());
expect (bigDsmall.isZero (), String ("(small/big)->N != 0: ") + bigDsmall.getText ());
bigDsmall = STAmount::divide (smallValue, bigNative, uint160 (), uint160 ());
expect (bigDsmall.isZero (), String ("(small/bigNative)->N != 0: ") + bigDsmall.getText ());
// very bad offer
uint64 r = STAmount::getRate (smallValue, bigValue);
expect (r == 0, "getRate(smallOut/bigIn) != 0");
// very good offer
r = STAmount::getRate (bigValue, smallValue);
expect (r == 0, "getRate(smallIn/bigOUt) != 0");
}
//--------------------------------------------------------------------------
void testRounding ()
{
// VFALCO TODO There are no actual tests here, just printed output?
// Change this to actually do something.
#if 0
beginTestCase ("rounding ");
uint64 value = 25000000000000000ull;
int offset = -14;
STAmount::canonicalizeRound (false, value, offset, true);
STAmount one (CURRENCY_ONE, ACCOUNT_ONE, 1);
STAmount two (CURRENCY_ONE, ACCOUNT_ONE, 2);
STAmount three (CURRENCY_ONE, ACCOUNT_ONE, 3);
STAmount oneThird1 = STAmount::divRound (one, three, CURRENCY_ONE, ACCOUNT_ONE, false);
STAmount oneThird2 = STAmount::divide (one, three, CURRENCY_ONE, ACCOUNT_ONE);
STAmount oneThird3 = STAmount::divRound (one, three, CURRENCY_ONE, ACCOUNT_ONE, true);
WriteLog (lsINFO, STAmount) << oneThird1;
WriteLog (lsINFO, STAmount) << oneThird2;
WriteLog (lsINFO, STAmount) << oneThird3;
STAmount twoThird1 = STAmount::divRound (two, three, CURRENCY_ONE, ACCOUNT_ONE, false);
STAmount twoThird2 = STAmount::divide (two, three, CURRENCY_ONE, ACCOUNT_ONE);
STAmount twoThird3 = STAmount::divRound (two, three, CURRENCY_ONE, ACCOUNT_ONE, true);
WriteLog (lsINFO, STAmount) << twoThird1;
WriteLog (lsINFO, STAmount) << twoThird2;
WriteLog (lsINFO, STAmount) << twoThird3;
STAmount oneA = STAmount::mulRound (oneThird1, three, CURRENCY_ONE, ACCOUNT_ONE, false);
STAmount oneB = STAmount::multiply (oneThird2, three, CURRENCY_ONE, ACCOUNT_ONE);
STAmount oneC = STAmount::mulRound (oneThird3, three, CURRENCY_ONE, ACCOUNT_ONE, true);
WriteLog (lsINFO, STAmount) << oneA;
WriteLog (lsINFO, STAmount) << oneB;
WriteLog (lsINFO, STAmount) << oneC;
STAmount fourThirdsA = STAmount::addRound (twoThird2, twoThird2, false);
STAmount fourThirdsB = twoThird2 + twoThird2;
STAmount fourThirdsC = STAmount::addRound (twoThird2, twoThird2, true);
WriteLog (lsINFO, STAmount) << fourThirdsA;
WriteLog (lsINFO, STAmount) << fourThirdsB;
WriteLog (lsINFO, STAmount) << fourThirdsC;
STAmount dripTest1 = STAmount::mulRound (twoThird2, two, uint160 (), uint160 (), false);
STAmount dripTest2 = STAmount::multiply (twoThird2, two, uint160 (), uint160 ());
STAmount dripTest3 = STAmount::mulRound (twoThird2, two, uint160 (), uint160 (), true);
WriteLog (lsINFO, STAmount) << dripTest1;
WriteLog (lsINFO, STAmount) << dripTest2;
WriteLog (lsINFO, STAmount) << dripTest3;
#endif
}
//--------------------------------------------------------------------------
void runTest ()
{
testSetValue ();
testNativeCurrency ();
testCustomCurrency ();
testArithmetic ();
testUnderflow ();
testRounding ();
}
};
static STAmountTests stAmountTests;