From 3d9589f01088f0d6de18916e59e4f6ef4a92feda Mon Sep 17 00:00:00 2001 From: seelabs Date: Sat, 3 Oct 2015 14:19:36 -0400 Subject: [PATCH] Add IOU and XRP Amounts --- Builds/VisualStudio2015/RippleD.vcxproj | 2 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/app/paths/Credit.cpp | 22 ++ src/ripple/app/paths/Credit.h | 15 + src/ripple/ledger/tests/PathSet.h | 5 + src/ripple/protocol/AmountSpec.h | 289 ++++++++++++++++++ src/ripple/protocol/IOUAmount.h | 7 + src/ripple/protocol/Quality.h | 99 +++++- src/ripple/protocol/STAmount.h | 9 + src/ripple/protocol/XRPAmount.h | 22 ++ src/ripple/protocol/impl/IOUAmount.cpp | 109 ++++++- 11 files changed, 565 insertions(+), 17 deletions(-) create mode 100644 src/ripple/protocol/AmountSpec.h diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 5efa6a7739..595c7794f5 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -2552,6 +2552,8 @@ + + diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index a7b487a980..ff25f6fe18 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -3099,6 +3099,9 @@ ripple\protocol + + ripple\protocol + ripple\protocol diff --git a/src/ripple/app/paths/Credit.cpp b/src/ripple/app/paths/Credit.cpp index 2b574d3a63..489da59b62 100644 --- a/src/ripple/app/paths/Credit.cpp +++ b/src/ripple/app/paths/Credit.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -48,6 +49,16 @@ creditLimit ( return result; } +IOUAmount +creditLimit2 ( + ReadView const& v, + AccountID const& acc, + AccountID const& iss, + Currency const& cur) +{ + return toAmount (creditLimit (v, acc, iss, cur)); +} + STAmount creditBalance ( ReadView const& view, AccountID const& account, @@ -72,4 +83,15 @@ STAmount creditBalance ( return result; } +IOUAmount +creditBalance2 ( + ReadView const& v, + AccountID const& acc, + AccountID const& iss, + Currency const& cur) +{ + return toAmount (creditBalance (v, acc, iss, cur)); +} + + } // ripple diff --git a/src/ripple/app/paths/Credit.h b/src/ripple/app/paths/Credit.h index f1e685d4ad..6e3f0aa9b3 100644 --- a/src/ripple/app/paths/Credit.h +++ b/src/ripple/app/paths/Credit.h @@ -22,6 +22,7 @@ #include #include +#include namespace ripple { @@ -38,6 +39,13 @@ STAmount creditLimit ( AccountID const& issuer, Currency const& currency); +IOUAmount +creditLimit2 ( + ReadView const& v, + AccountID const& acc, + AccountID const& iss, + Currency const& cur); + /** Returns the amount of IOUs issued by issuer that are held by an account @param ledger the ledger to check against. @param account the account of interest. @@ -50,6 +58,13 @@ STAmount creditBalance ( AccountID const& issuer, Currency const& currency); +IOUAmount +creditBalance2 ( + ReadView const& v, + AccountID const& acc, + AccountID const& iss, + Currency const& cur); + } // ripple #endif diff --git a/src/ripple/ledger/tests/PathSet.h b/src/ripple/ledger/tests/PathSet.h index 579e6baa79..8a9b06c02b 100644 --- a/src/ripple/ledger/tests/PathSet.h +++ b/src/ripple/ledger/tests/PathSet.h @@ -17,6 +17,9 @@ */ //============================================================================== +#ifndef RIPPLE_LEDGER_TESTS_PATHSET_H_INCLUDED +#define RIPPLE_LEDGER_TESTS_PATHSET_H_INCLUDED + #include #include #include @@ -134,3 +137,5 @@ private: } // test } // ripple + +#endif diff --git a/src/ripple/protocol/AmountSpec.h b/src/ripple/protocol/AmountSpec.h new file mode 100644 index 0000000000..1c04a8a87f --- /dev/null +++ b/src/ripple/protocol/AmountSpec.h @@ -0,0 +1,289 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_AMOUNTSPEC_H_INCLUDED +#define RIPPLE_PROTOCOL_AMOUNTSPEC_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +struct AmountSpec +{ + bool native; + union + { + XRPAmount xrp; + IOUAmount iou; + }; + boost::optional issuer; + boost::optional currency; + + private: + friend + std::ostream& + operator << ( + std::ostream& stream, + AmountSpec const& amt) + { + if (amt.native) + stream << to_string (amt.xrp); + else + stream << to_string (amt.iou); + if (amt.currency) + stream << "/(" << *amt.currency << ")"; + if (amt.issuer) + stream << "/" << *amt.issuer << ""; + return stream; + } +}; + +struct EitherAmount +{ +#ifndef NDEBUG + bool native = false; +#endif + + union + { + IOUAmount iou; + XRPAmount xrp; + }; + + EitherAmount () = default; + + explicit + EitherAmount (IOUAmount const& a) + :iou(a) + { + } + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push + // ignore warning about half of iou amount being uninitialized +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + explicit + EitherAmount (XRPAmount const& a) + :xrp(a) + { +#ifndef NDEBUG + native = true; +#endif + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + + explicit + EitherAmount (AmountSpec const& a) + { +#ifndef NDEBUG + native = a.native; +#endif + if (a.native) + xrp = a.xrp; + else + iou = a.iou; + } +}; + +template +T& +get (EitherAmount& amt) +{ + static_assert(sizeof(T) == -1, "Must used specialized function"); + return T(0); +} + +template <> +inline +IOUAmount& +get (EitherAmount& amt) +{ + assert (!amt.native); + return amt.iou; +} + +template <> +inline +XRPAmount& +get (EitherAmount& amt) +{ + assert (amt.native); + return amt.xrp; +} + +template +T const& +get (EitherAmount const& amt) +{ + static_assert(sizeof(T) == -1, "Must used specialized function"); + return T(0); +} + +template <> +inline +IOUAmount const& +get (EitherAmount const& amt) +{ + assert (!amt.native); + return amt.iou; +} + +template <> +inline +XRPAmount const& +get (EitherAmount const& amt) +{ + assert (amt.native); + return amt.xrp; +} + +inline +AmountSpec +toAmountSpec (STAmount const& amt) +{ + assert (amt.mantissa () < std::numeric_limits::max ()); + bool const isNeg = amt.negative (); + std::int64_t const sMant = + isNeg ? - std::int64_t (amt.mantissa ()) : amt.mantissa (); + AmountSpec result; + + result.native = isXRP (amt); + if (result.native) + { + result.xrp = XRPAmount (sMant); + } + else + { + result.iou = IOUAmount (sMant, amt.exponent ()); + result.issuer = amt.issue().account; + result.currency = amt.issue().currency; + } + + return result; +} + +inline +AmountSpec +toAmountSpec ( + EitherAmount const& ea, + boost::optional const& c) +{ + AmountSpec r; + r.native = (!c || isXRP (*c)); + r.currency = c; + assert (ea.native == r.native); + if (r.native) + { + r.xrp = ea.xrp; + } + else + { + r.iou = ea.iou; + } + return r; +} + +inline +STAmount +toSTAmount (IOUAmount const& iou, Issue const& iss) +{ + bool const isNeg = iou.signum() < 0; + std::uint64_t const umant = isNeg ? - iou.mantissa () : iou.mantissa (); + return STAmount (iss, umant, iou.exponent (), /*native*/ false, isNeg, + STAmount::unchecked ()); +} + +inline +STAmount +toSTAmount (IOUAmount const& iou) +{ + return toSTAmount (iou, noIssue ()); +} + +inline +STAmount +toSTAmount (XRPAmount const& xrp) +{ + bool const isNeg = xrp.signum() < 0; + std::uint64_t const umant = isNeg ? - xrp.drops () : xrp.drops (); + return STAmount (umant, isNeg); +} + +inline +STAmount +toSTAmount (XRPAmount const& xrp, Issue const& iss) +{ + assert (isXRP(iss.account) && isXRP(iss.currency)); + return toSTAmount (xrp); +} + +template +T +toAmount (STAmount const& amt) +{ + static_assert(sizeof(T) == -1, "Must used specialized function"); + return T(0); +} + +template <> +inline +STAmount +toAmount (STAmount const& amt) +{ + return amt; +} + +template <> +inline +IOUAmount +toAmount (STAmount const& amt) +{ + assert (amt.mantissa () < std::numeric_limits::max ()); + bool const isNeg = amt.negative (); + std::int64_t const sMant = + isNeg ? - std::int64_t (amt.mantissa ()) : amt.mantissa (); + + assert (! isXRP (amt)); + return IOUAmount (sMant, amt.exponent ()); +} + +template <> +inline +XRPAmount +toAmount (STAmount const& amt) +{ + assert (amt.mantissa () < std::numeric_limits::max ()); + bool const isNeg = amt.negative (); + std::int64_t const sMant = + isNeg ? - std::int64_t (amt.mantissa ()) : amt.mantissa (); + AmountSpec result; + + assert (isXRP (amt)); + return XRPAmount (sMant); +} + + +} + +#endif diff --git a/src/ripple/protocol/IOUAmount.h b/src/ripple/protocol/IOUAmount.h index 54818e864e..013626d54d 100644 --- a/src/ripple/protocol/IOUAmount.h +++ b/src/ripple/protocol/IOUAmount.h @@ -140,6 +140,13 @@ public: std::string to_string (IOUAmount const& amount); +IOUAmount +mulRatio ( + IOUAmount const& amt, + std::uint32_t num, + std::uint32_t den, + bool roundUp); + } #endif diff --git a/src/ripple/protocol/Quality.h b/src/ripple/protocol/Quality.h index 60b564a0d1..f1faa13cfc 100644 --- a/src/ripple/protocol/Quality.h +++ b/src/ripple/protocol/Quality.h @@ -20,7 +20,11 @@ #ifndef RIPPLE_PROTOCOL_QUALITY_H_INCLUDED #define RIPPLE_PROTOCOL_QUALITY_H_INCLUDED +#include +#include #include +#include + #include #include @@ -35,11 +39,18 @@ namespace ripple { For offers, "in" is always TakerPays and "out" is always TakerGets. */ -struct Amounts +template +struct TAmounts { - Amounts() = default; + TAmounts() = default; - Amounts (STAmount const& in_, STAmount const& out_) + TAmounts (beast::Zero, beast::Zero) + : in (beast::zero) + , out (beast::zero) + { + } + + TAmounts (TIn const& in_, TOut const& out_) : in (in_) , out (out_) { @@ -52,20 +63,46 @@ struct Amounts return in <= zero || out <= zero; } - STAmount in; - STAmount out; + TAmounts& operator+=(TAmounts const& rhs) + { + in += rhs.in; + out += rhs.out; + return *this; + } + + TAmounts& operator-=(TAmounts const& rhs) + { + in -= rhs.in; + out -= rhs.out; + return *this; + } + + TIn in; + TOut out; }; -inline +template +TAmounts make_Amounts(TIn const& in, TOut const& out) +{ + return TAmounts(in, out); +} + +using Amounts = TAmounts; + +template bool -operator== (Amounts const& lhs, Amounts const& rhs) noexcept +operator== ( + TAmounts const& lhs, + TAmounts const& rhs) noexcept { return lhs.in == rhs.in && lhs.out == rhs.out; } -inline +template bool -operator!= (Amounts const& lhs, Amounts const& rhs) noexcept +operator!= ( + TAmounts const& lhs, + TAmounts const& rhs) noexcept { return ! (lhs == rhs); } @@ -101,6 +138,13 @@ public: explicit Quality (Amounts const& amount); + /** Create a quality from the ratio of two amounts. */ + template + Quality (TOut const& out, TIn const& in) + : Quality (Amounts (toSTAmount (in), + toSTAmount (out))) + {} + /** Advances to the next higher quality level. */ /** @{ */ Quality& @@ -133,6 +177,21 @@ public: Amounts ceil_in (Amounts const& amount, STAmount const& limit) const; + template + TAmounts + ceil_in (TAmounts const& amount, TIn const& limit) const + { + if (amount.in <= limit) + return amount; + + // Use the existing STAmount implementation for now, but consider + // replacing with code specific to IOUAMount and XRPAmount + Amounts stAmt (toSTAmount (amount.in), toSTAmount (amount.out)); + STAmount stLim (toSTAmount (limit)); + auto const stRes = ceil_in (stAmt, stLim); + return TAmounts (toAmount (stRes.in), toAmount (stRes.out)); + } + /** Returns the scaled amount with out capped. Math is avoided if the result is exact. The input is clamped to prevent money creation. @@ -140,6 +199,21 @@ public: Amounts ceil_out (Amounts const& amount, STAmount const& limit) const; + template + TAmounts + ceil_out (TAmounts const& amount, TOut const& limit) const + { + if (amount.out <= limit) + return amount; + + // Use the existing STAmount implementation for now, but consider + // replacing with code specific to IOUAMount and XRPAmount + Amounts stAmt (toSTAmount (amount.in), toSTAmount (amount.out)); + STAmount stLim (toSTAmount (limit)); + auto const stRes = ceil_out (stAmt, stLim); + return TAmounts (toAmount (stRes.in), toAmount (stRes.out)); + } + /** Returns `true` if lhs is lower quality than `rhs`. Lower quality means the taker receives a worse deal. Higher quality is better for the taker. @@ -151,6 +225,13 @@ public: return lhs.m_value > rhs.m_value; } + friend + bool + operator> (Quality const& lhs, Quality const& rhs) noexcept + { + return lhs.m_value < rhs.m_value; + } + friend bool operator== (Quality const& lhs, Quality const& rhs) noexcept diff --git a/src/ripple/protocol/STAmount.h b/src/ripple/protocol/STAmount.h index 5bff0828ae..c9ddc5d606 100644 --- a/src/ripple/protocol/STAmount.h +++ b/src/ripple/protocol/STAmount.h @@ -312,6 +312,15 @@ amountFromJson (SField const& name, Json::Value const& v); bool amountFromJsonNoThrow (STAmount& result, Json::Value const& jvSource); +// IOUAmount and XRPAmount define toSTAmount, defining this +// trivial conversion here makes writing generic code easier +inline +STAmount const& +toSTAmount (STAmount const& a) +{ + return a; +} + //------------------------------------------------------------------------------ // // Observers diff --git a/src/ripple/protocol/XRPAmount.h b/src/ripple/protocol/XRPAmount.h index 37577bbe80..7c2fd3973e 100644 --- a/src/ripple/protocol/XRPAmount.h +++ b/src/ripple/protocol/XRPAmount.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -134,6 +135,27 @@ to_string (XRPAmount const& amount) return std::to_string (amount.drops ()); } +inline +XRPAmount +mulRatio ( + XRPAmount const& amt, + std::uint32_t num, + std::uint32_t den, + bool roundUp) +{ + using namespace boost::multiprecision; + int128_t const den128 (den); + int128_t const num128 (num); + int128_t const amt128 (amt.drops ()); + auto const m = int128_t (amt.drops ()) * num; + auto r = m / den; + if (roundUp && r >= 0 && (m % den)) + r += 1; + if (r > std::numeric_limits::max ()) + throw std::overflow_error ("XRP mulRatio overflow"); + return XRPAmount (r.convert_to ()); +} + /** Returns true if the amount does not exceed the initial XRP in existence. */ inline bool isLegalAmount (XRPAmount const& amount) diff --git a/src/ripple/protocol/impl/IOUAmount.cpp b/src/ripple/protocol/impl/IOUAmount.cpp index 125aeff9b5..9cdd7863c7 100644 --- a/src/ripple/protocol/impl/IOUAmount.cpp +++ b/src/ripple/protocol/impl/IOUAmount.cpp @@ -20,23 +20,24 @@ #include #include #include +#include #include +#include #include #include namespace ripple { +/* The range for the mantissa when normalized */ +static std::int64_t const minMantissa = 1000000000000000ull; +static std::int64_t const maxMantissa = 9999999999999999ull; +/* The range for the exponent when normalized */ +static int const minExponent = -96; +static int const maxExponent = 80; + void IOUAmount::normalize () { - /* The range for the exponent when normalized */ - static int const minExponent = -96; - static int const maxExponent = 80; - - /* The range for the mantissa when normalized */ - static std::int64_t const minMantissa = 1000000000000000ull; - static std::int64_t const maxMantissa = 9999999999999999ull; - if (mantissa_ == 0) { *this = zero; @@ -244,4 +245,96 @@ to_string (IOUAmount const& amount) return ret; } +IOUAmount +mulRatio ( + IOUAmount const& amt, + std::uint32_t num, + std::uint32_t den, + bool roundUp) +{ + using namespace boost::multiprecision; + + static std::vector const logTable = [] + { + std::vector result; + result.reserve (30); // 2^96 is largest intermediate result size + uint128_t cur (1); + for (int i = 0; i < 30; ++i) + { + result.push_back (cur); + cur *= 10; + }; + return result; + }(); + // Note: Returns -1 for v == 0 + static auto log10Floor = [](uint128_t const& v) + { + auto const l = std::lower_bound (logTable.begin (), logTable.end (), v); + int index = std::distance (logTable.begin (), l); + if (*l != v) + --index; + return index; + }; + static auto log10Ceil = [](uint128_t const& v) + { + auto const l = std::lower_bound (logTable.begin (), logTable.end (), v); + return int(std::distance (logTable.begin (), l)); + }; + static auto const fl64 = + log10Floor (std::numeric_limits::max ()); + bool const neg = amt.mantissa () < 0; + uint128_t const den128 (den); + uint128_t const mul = + uint128_t (neg ? -amt.mantissa () : amt.mantissa ()) * uint128_t (num); + auto low = mul / den128; + uint128_t rem (mul - low * den128); + + int exponent = amt.exponent (); + + if (rem) + { + auto const roomToGrow = fl64 - log10Ceil (low); + if (roomToGrow > 0) + { + exponent -= roomToGrow; + low *= logTable[roomToGrow]; + rem *= logTable[roomToGrow]; + } + auto const addRem = rem / den128; + low += addRem; + rem = rem - addRem * den128; + } + + bool hasRem = bool(rem); + auto const mustShrink = log10Ceil (low) - fl64; + if (mustShrink > 0) + { + uint128_t const sav (low); + exponent += mustShrink; + low /= logTable[mustShrink]; + if (!hasRem && roundUp) + hasRem = bool(sav - low * logTable[mustShrink]); + } + + std::int64_t mantissa = low.convert_to (); + + // normalize before rounding + if (neg) + mantissa *= -1; + + IOUAmount result (mantissa, exponent); + + if (roundUp && hasRem && !neg) + { + if (!result) + { + return IOUAmount (minMantissa, minExponent); + } + return IOUAmount (result.mantissa() + 1, result.exponent()); + } + + return result; +} + + }