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()); // std::cerr << "currency: " << sIso << std::endl; 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 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 = lexical_cast_st(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 = ::isZero(vucZeros.begin(), vucZeros.size()) // Leading zeros && ::isZero(vucVersion.begin(), vucVersion.size()) // Zero version && ::isZero(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 = lexical_cast_s(smMatch[2]); mOffset = 0; } else { // integer and fraction mValue = lexical_cast_s(smMatch[2] + smMatch[4]); mOffset = -(smMatch[4].length()); } if (smMatch[5].matched) { // we have an exponent if (smMatch[6].matched && (smMatch[6] == "-")) mOffset -= lexical_cast_s(smMatch[7]); else mOffset += lexical_cast_s(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 = 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(mOffset + 512 + 97) << (64 - 10))); else // 256 = positive s.add64(mValue | (static_cast(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(value), false) : STAmount(name, static_cast(-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(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(mValue); return static_cast(mValue); } void STAmount::setSNValue(int64 v) { if (!mIsNative) throw std::runtime_error("not native"); if (v > 0) { mIsNegative = false; mValue = static_cast(v); } else { mIsNegative = true; mValue = static_cast(-v); } } std::string STAmount::getRaw() const { // show raw internal form if (mValue == 0) return "0"; if (mIsNative) { if (mIsNegative) return std::string("-") + lexical_cast_i(mValue); else return lexical_cast_i(mValue); } if (mIsNegative) return mCurrency.GetHex() + ": -" + lexical_cast_i(mValue) + "e" + lexical_cast_i(mOffset); else return mCurrency.GetHex() + ": " + lexical_cast_i(mValue) + "e" + lexical_cast_i(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("-") + lexical_cast_i(mValue); else return lexical_cast_i(mValue); } if ((mOffset != 0) && ((mOffset < -25) || (mOffset > -5))) { if (mIsNegative) return std::string("-") + lexical_cast_i(mValue) + "e" + lexical_cast_i(mOffset); else return lexical_cast_i(mValue) + "e" + lexical_cast_i(mOffset); } std::string val = "000000000000000000000000000"; val += lexical_cast_i(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(&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(v)); else *this += STAmount(mCurrency, v); return *this; } STAmount& STAmount::operator-=(uint64 v) { if (mIsNative) setSNValue(getSNValue() - static_cast(v)); else *this -= STAmount(mCurrency, v); return *this; } bool STAmount::operator<(uint64 v) const { return getSNValue() < static_cast(v); } bool STAmount::operator>(uint64 v) const { return getSNValue() > static_cast(v); } bool STAmount::operator<=(uint64 v) const { return getSNValue() <= static_cast(v); } bool STAmount::operator>=(uint64 v) const { return getSNValue() >= static_cast(v); } STAmount STAmount::operator+(uint64 v) const { return STAmount(getFName(), getSNValue() + static_cast(v)); } STAmount STAmount::operator-(uint64 v) const { return STAmount(getFName(), getSNValue() - static_cast(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(mValue) * pow(10.0, mOffset); return static_cast(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(v1.mValue), vv2 = static_cast(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(v1.mValue), vv2 = static_cast(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(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 s(dynamic_cast(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; } // For unit tests: static STAmount serdes(const STAmount &s) { Serializer ser; s.add(ser); SerializerIterator sit(ser); return STAmount::deserialize(sit); } BOOST_AUTO_TEST_SUITE(amount) BOOST_AUTO_TEST_CASE( setValue_test ) { 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"); BOOST_CHECK_MESSAGE(1 == saTmp.getNValue(), "integer failed"); } BOOST_AUTO_TEST_CASE( NativeCurrency_test ) { STAmount zero, one(1), hundred(100); if (serdes(zero) != zero) BOOST_FAIL("STAmount fail"); if (serdes(one) != one) BOOST_FAIL("STAmount fail"); if (serdes(hundred) != hundred) BOOST_FAIL("STAmount fail"); if (!zero.isNative()) BOOST_FAIL("STAmount fail"); if (!hundred.isNative()) BOOST_FAIL("STAmount fail"); if (!zero.isZero()) BOOST_FAIL("STAmount fail"); if (one.isZero()) BOOST_FAIL("STAmount fail"); if (hundred.isZero()) BOOST_FAIL("STAmount fail"); if ((zero < zero)) BOOST_FAIL("STAmount fail"); if (!(zero < one)) BOOST_FAIL("STAmount fail"); if (!(zero < hundred)) BOOST_FAIL("STAmount fail"); if ((one < zero)) BOOST_FAIL("STAmount fail"); if ((one < one)) BOOST_FAIL("STAmount fail"); if (!(one < hundred)) BOOST_FAIL("STAmount fail"); if ((hundred < zero)) BOOST_FAIL("STAmount fail"); if ((hundred < one)) BOOST_FAIL("STAmount fail"); if ((hundred < hundred)) BOOST_FAIL("STAmount fail"); if ((zero > zero)) BOOST_FAIL("STAmount fail"); if ((zero > one)) BOOST_FAIL("STAmount fail"); if ((zero > hundred)) BOOST_FAIL("STAmount fail"); if (!(one > zero)) BOOST_FAIL("STAmount fail"); if ((one > one)) BOOST_FAIL("STAmount fail"); if ((one > hundred)) BOOST_FAIL("STAmount fail"); if (!(hundred > zero)) BOOST_FAIL("STAmount fail"); if (!(hundred > one)) BOOST_FAIL("STAmount fail"); if ((hundred > hundred)) BOOST_FAIL("STAmount fail"); if (!(zero <= zero)) BOOST_FAIL("STAmount fail"); if (!(zero <= one)) BOOST_FAIL("STAmount fail"); if (!(zero <= hundred)) BOOST_FAIL("STAmount fail"); if ((one <= zero)) BOOST_FAIL("STAmount fail"); if (!(one <= one)) BOOST_FAIL("STAmount fail"); if (!(one <= hundred)) BOOST_FAIL("STAmount fail"); if ((hundred <= zero)) BOOST_FAIL("STAmount fail"); if ((hundred <= one)) BOOST_FAIL("STAmount fail"); if (!(hundred <= hundred)) BOOST_FAIL("STAmount fail"); if (!(zero >= zero)) BOOST_FAIL("STAmount fail"); if ((zero >= one)) BOOST_FAIL("STAmount fail"); if ((zero >= hundred)) BOOST_FAIL("STAmount fail"); if (!(one >= zero)) BOOST_FAIL("STAmount fail"); if (!(one >= one)) BOOST_FAIL("STAmount fail"); if ((one >= hundred)) BOOST_FAIL("STAmount fail"); if (!(hundred >= zero)) BOOST_FAIL("STAmount fail"); if (!(hundred >= one)) BOOST_FAIL("STAmount fail"); if (!(hundred >= hundred)) BOOST_FAIL("STAmount fail"); if (!(zero == zero)) BOOST_FAIL("STAmount fail"); if ((zero == one)) BOOST_FAIL("STAmount fail"); if ((zero == hundred)) BOOST_FAIL("STAmount fail"); if ((one == zero)) BOOST_FAIL("STAmount fail"); if (!(one == one)) BOOST_FAIL("STAmount fail"); if ((one == hundred)) BOOST_FAIL("STAmount fail"); if ((hundred == zero)) BOOST_FAIL("STAmount fail"); if ((hundred == one)) BOOST_FAIL("STAmount fail"); if (!(hundred == hundred)) BOOST_FAIL("STAmount fail"); if ((zero != zero)) BOOST_FAIL("STAmount fail"); if (!(zero != one)) BOOST_FAIL("STAmount fail"); if (!(zero != hundred)) BOOST_FAIL("STAmount fail"); if (!(one != zero)) BOOST_FAIL("STAmount fail"); if ((one != one)) BOOST_FAIL("STAmount fail"); if (!(one != hundred)) BOOST_FAIL("STAmount fail"); if (!(hundred != zero)) BOOST_FAIL("STAmount fail"); if (!(hundred != one)) BOOST_FAIL("STAmount fail"); if ((hundred != hundred)) BOOST_FAIL("STAmount fail"); if (STAmount().getText() != "0") BOOST_FAIL("STAmount fail"); if (STAmount(31).getText() != "31") BOOST_FAIL("STAmount fail"); if (STAmount(310).getText() != "310") BOOST_FAIL("STAmount fail"); BOOST_TEST_MESSAGE("Amount NC Complete"); } BOOST_AUTO_TEST_CASE( CustomCurrency_test ) { STAmount zero(CURRENCY_ONE, ACCOUNT_ONE), one(CURRENCY_ONE, ACCOUNT_ONE, 1), hundred(CURRENCY_ONE, ACCOUNT_ONE, 100); serdes(one).getRaw(); if (serdes(zero) != zero) BOOST_FAIL("STAmount fail"); if (serdes(one) != one) BOOST_FAIL("STAmount fail"); if (serdes(hundred) != hundred) BOOST_FAIL("STAmount fail"); if (zero.isNative()) BOOST_FAIL("STAmount fail"); if (hundred.isNative()) BOOST_FAIL("STAmount fail"); if (!zero.isZero()) BOOST_FAIL("STAmount fail"); if (one.isZero()) BOOST_FAIL("STAmount fail"); if (hundred.isZero()) BOOST_FAIL("STAmount fail"); if ((zero < zero)) BOOST_FAIL("STAmount fail"); if (!(zero < one)) BOOST_FAIL("STAmount fail"); if (!(zero < hundred)) BOOST_FAIL("STAmount fail"); if ((one < zero)) BOOST_FAIL("STAmount fail"); if ((one < one)) BOOST_FAIL("STAmount fail"); if (!(one < hundred)) BOOST_FAIL("STAmount fail"); if ((hundred < zero)) BOOST_FAIL("STAmount fail"); if ((hundred < one)) BOOST_FAIL("STAmount fail"); if ((hundred < hundred)) BOOST_FAIL("STAmount fail"); if ((zero > zero)) BOOST_FAIL("STAmount fail"); if ((zero > one)) BOOST_FAIL("STAmount fail"); if ((zero > hundred)) BOOST_FAIL("STAmount fail"); if (!(one > zero)) BOOST_FAIL("STAmount fail"); if ((one > one)) BOOST_FAIL("STAmount fail"); if ((one > hundred)) BOOST_FAIL("STAmount fail"); if (!(hundred > zero)) BOOST_FAIL("STAmount fail"); if (!(hundred > one)) BOOST_FAIL("STAmount fail"); if ((hundred > hundred)) BOOST_FAIL("STAmount fail"); if (!(zero <= zero)) BOOST_FAIL("STAmount fail"); if (!(zero <= one)) BOOST_FAIL("STAmount fail"); if (!(zero <= hundred)) BOOST_FAIL("STAmount fail"); if ((one <= zero)) BOOST_FAIL("STAmount fail"); if (!(one <= one)) BOOST_FAIL("STAmount fail"); if (!(one <= hundred)) BOOST_FAIL("STAmount fail"); if ((hundred <= zero)) BOOST_FAIL("STAmount fail"); if ((hundred <= one)) BOOST_FAIL("STAmount fail"); if (!(hundred <= hundred)) BOOST_FAIL("STAmount fail"); if (!(zero >= zero)) BOOST_FAIL("STAmount fail"); if ((zero >= one)) BOOST_FAIL("STAmount fail"); if ((zero >= hundred)) BOOST_FAIL("STAmount fail"); if (!(one >= zero)) BOOST_FAIL("STAmount fail"); if (!(one >= one)) BOOST_FAIL("STAmount fail"); if ((one >= hundred)) BOOST_FAIL("STAmount fail"); if (!(hundred >= zero)) BOOST_FAIL("STAmount fail"); if (!(hundred >= one)) BOOST_FAIL("STAmount fail"); if (!(hundred >= hundred)) BOOST_FAIL("STAmount fail"); if (!(zero == zero)) BOOST_FAIL("STAmount fail"); if ((zero == one)) BOOST_FAIL("STAmount fail"); if ((zero == hundred)) BOOST_FAIL("STAmount fail"); if ((one == zero)) BOOST_FAIL("STAmount fail"); if (!(one == one)) BOOST_FAIL("STAmount fail"); if ((one == hundred)) BOOST_FAIL("STAmount fail"); if ((hundred == zero)) BOOST_FAIL("STAmount fail"); if ((hundred == one)) BOOST_FAIL("STAmount fail"); if (!(hundred == hundred)) BOOST_FAIL("STAmount fail"); if ((zero != zero)) BOOST_FAIL("STAmount fail"); if (!(zero != one)) BOOST_FAIL("STAmount fail"); if (!(zero != hundred)) BOOST_FAIL("STAmount fail"); if (!(one != zero)) BOOST_FAIL("STAmount fail"); if ((one != one)) BOOST_FAIL("STAmount fail"); if (!(one != hundred)) BOOST_FAIL("STAmount fail"); if (!(hundred != zero)) BOOST_FAIL("STAmount fail"); if (!(hundred != one)) BOOST_FAIL("STAmount fail"); if ((hundred != hundred)) BOOST_FAIL("STAmount fail"); if (STAmount(CURRENCY_ONE, ACCOUNT_ONE).getText() != "0") BOOST_FAIL("STAmount fail"); if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31).getText() != "31") BOOST_FAIL("STAmount fail"); if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31,1).getText() != "310") BOOST_FAIL("STAmount fail"); if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31,-1).getText() != "3.1") BOOST_FAIL("STAmount fail"); if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31,-2).getText() != "0.31") BOOST_FAIL("STAmount fail"); if (STAmount::multiply(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "60") BOOST_FAIL("STAmount multiply fail 1"); if (STAmount::multiply(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount(3), uint160(), ACCOUNT_XRP).getText() != "60") BOOST_FAIL("STAmount multiply fail 2"); if (STAmount::multiply(STAmount(20), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "60") BOOST_FAIL("STAmount multiply fail 3"); if (STAmount::multiply(STAmount(20), STAmount(3), uint160(), ACCOUNT_XRP).getText() != "60") BOOST_FAIL("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(); BOOST_FAIL("STAmount divide fail"); } if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(3), uint160(), ACCOUNT_XRP).getText() != "20") BOOST_FAIL("STAmount divide fail"); if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "20") BOOST_FAIL("STAmount divide fail"); if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 3), uint160(), ACCOUNT_XRP).getText() != "20") BOOST_FAIL("STAmount divide fail"); STAmount a1(CURRENCY_ONE, ACCOUNT_ONE, 60), a2 (CURRENCY_ONE, ACCOUNT_ONE, 10, -1); if (STAmount::divide(a2, a1, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate(STAmount::getRate(a1, a2))) BOOST_FAIL("STAmount setRate(getRate) fail"); if (STAmount::divide(a1, a2, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate(STAmount::getRate(a2, a1))) BOOST_FAIL("STAmount setRate(getRate) fail"); BOOST_TEST_MESSAGE("Amount CC Complete"); } static 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); if (res.isNative()) BOOST_FAIL("Product is native"); res.roundSelf(); STAmount cmp(CURRENCY_ONE, ACCOUNT_ONE, (n * m) / d); if (cmp.isNative()) BOOST_FAIL("Comparison amount is native"); if (res == cmp) return true; cmp.throwComparable(res); WriteLog (lsWARNING, STAmount) << "(" << num.getText() << "/" << den.getText() << ") X " << mul.getText() << " = " << res.getText() << " not " << cmp.getText(); BOOST_FAIL("Round fail"); return false; } static 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)); if (prod1.isNative()) BOOST_FAIL("product is native"); STAmount prod2(CURRENCY_ONE, ACCOUNT_ONE, static_cast(a) * static_cast(b)); if (prod1 != prod2) { WriteLog (lsWARNING, STAmount) << "nn(" << aa.getFullText() << " * " << bb.getFullText() << ") = " << prod1.getFullText() << " not " << prod2.getFullText(); BOOST_WARN("Multiplication result is not exact"); } 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(); BOOST_WARN("Multiplication result is not exact"); } } BOOST_AUTO_TEST_CASE( CurrencyMulDivTests ) { 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); BOOST_FAIL("setull64/getull64 failure"); } } // Test currency multiplication and division operations such as // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded if (STAmount::getRate(STAmount(1), STAmount(10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) BOOST_FAIL("STAmount getRate fail 1"); if (STAmount::getRate(STAmount(10), STAmount(1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) BOOST_FAIL("STAmount getRate fail 2"); if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) BOOST_FAIL("STAmount getRate fail 3"); if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) BOOST_FAIL("STAmount getRate fail 4"); if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount(10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) BOOST_FAIL("STAmount getRate fail 5"); if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount(1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) BOOST_FAIL("STAmount getRate fail 6"); if (STAmount::getRate(STAmount(1), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) BOOST_FAIL("STAmount getRate fail 7"); if (STAmount::getRate(STAmount(10), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) BOOST_FAIL("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); } BOOST_AUTO_TEST_CASE( UnderFlowTests ) { 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); if (!smallXsmall.isZero()) BOOST_FAIL("STAmount: smallXsmall != 0"); STAmount bigDsmall = STAmount::divide(smallValue, bigValue, CURRENCY_ONE, ACCOUNT_ONE); if (!bigDsmall.isZero()) BOOST_FAIL("STAmount: small/big != 0: " << bigDsmall); bigDsmall = STAmount::divide(smallValue, bigNative, CURRENCY_ONE, uint160()); if (!bigDsmall.isZero()) BOOST_FAIL("STAmount: small/bigNative != 0: " << bigDsmall); bigDsmall = STAmount::divide(smallValue, bigValue, uint160(), uint160()); if (!bigDsmall.isZero()) BOOST_FAIL("STAmount: (small/big)->N != 0: " << bigDsmall); bigDsmall = STAmount::divide(smallValue, bigNative, uint160(), uint160()); if (!bigDsmall.isZero()) BOOST_FAIL("STAmount: (small/bigNative)->N != 0: " << bigDsmall); // very bad offer uint64 r = STAmount::getRate(smallValue, bigValue); if (r != 0) BOOST_FAIL("STAmount: getRate(smallOut/bigIn) != 0" << r); // very good offer r = STAmount::getRate(bigValue, smallValue); if (r != 0) BOOST_FAIL("STAmount:: getRate(smallIn/bigOUt) != 0" << r); } BOOST_AUTO_TEST_SUITE_END() // vim:ts=4