diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index d957618a8b4..cd0cff5569b 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -126,6 +126,7 @@ target_sources (xrpl_core PRIVATE src/ripple/protocol/impl/AccountID.cpp src/ripple/protocol/impl/AMMCore.cpp src/ripple/protocol/impl/Asset.cpp + src/ripple/protocol/impl/BaseAmount.cpp src/ripple/protocol/impl/Book.cpp src/ripple/protocol/impl/BuildInfo.cpp src/ripple/protocol/impl/ErrorCodes.cpp @@ -310,6 +311,7 @@ install ( src/ripple/protocol/AmountConversions.h src/ripple/protocol/ApiVersion.h src/ripple/protocol/Asset.h + src/ripple/protocol/BaseAmount.h src/ripple/protocol/Book.h src/ripple/protocol/BuildInfo.h src/ripple/protocol/ErrorCodes.h diff --git a/src/ripple/basics/MPTAmount.h b/src/ripple/basics/MPTAmount.h index 2678c56040e..abc7359a0a7 100644 --- a/src/ripple/basics/MPTAmount.h +++ b/src/ripple/basics/MPTAmount.h @@ -41,10 +41,10 @@ class MPTAmount : private boost::totally_ordered, private boost::additive { public: - using mpt_type = std::int64_t; + using value_type = std::int64_t; protected: - mpt_type mpt_; + value_type mpt_; public: MPTAmount() = default; @@ -56,7 +56,7 @@ class MPTAmount : private boost::totally_ordered, { } - constexpr explicit MPTAmount(mpt_type value) : mpt_(value) + constexpr explicit MPTAmount(value_type value) : mpt_(value) { } @@ -67,20 +67,20 @@ class MPTAmount : private boost::totally_ordered, } MPTAmount& - operator=(mpt_type value) + operator=(value_type value) { mpt_ = value; return *this; } constexpr MPTAmount - operator*(mpt_type const& rhs) const + operator*(value_type const& rhs) const { return MPTAmount{mpt_ * rhs}; } friend constexpr MPTAmount - operator*(mpt_type lhs, MPTAmount const& rhs) + operator*(value_type lhs, MPTAmount const& rhs) { // multiplication is commutative return rhs * lhs; @@ -101,21 +101,21 @@ class MPTAmount : private boost::totally_ordered, } MPTAmount& - operator+=(mpt_type const& rhs) + operator+=(value_type const& rhs) { mpt_ += rhs; return *this; } MPTAmount& - operator-=(mpt_type const& rhs) + operator-=(value_type const& rhs) { mpt_ -= rhs; return *this; } MPTAmount& - operator*=(mpt_type const& rhs) + operator*=(value_type const& rhs) { mpt_ *= rhs; return *this; @@ -134,7 +134,7 @@ class MPTAmount : private boost::totally_ordered, } bool - operator==(mpt_type other) const + operator==(value_type other) const { return mpt_ == other; } @@ -162,7 +162,7 @@ class MPTAmount : private boost::totally_ordered, jsonClipped() const { static_assert( - std::is_signed_v && std::is_integral_v, + std::is_signed_v && std::is_integral_v, "Expected MPTAmount to be a signed integral type"); constexpr auto min = std::numeric_limits::min(); @@ -179,7 +179,7 @@ class MPTAmount : private boost::totally_ordered, function unless the type has been abstracted away, e.g. in a templated function. */ - constexpr mpt_type + constexpr value_type mpt() const { return mpt_; @@ -236,9 +236,9 @@ mulRatio( if (neg && !roundUp) r -= 1; } - if (r > std::numeric_limits::max()) + if (r > std::numeric_limits::max()) Throw("XRP mulRatio overflow"); - return MPTAmount(r.convert_to()); + return MPTAmount(r.convert_to()); } } // namespace ripple diff --git a/src/ripple/protocol/BaseAmount.h b/src/ripple/protocol/BaseAmount.h new file mode 100644 index 00000000000..a594eb25e06 --- /dev/null +++ b/src/ripple/protocol/BaseAmount.h @@ -0,0 +1,1481 @@ +//------------------------------------------------------------------------------ +/* + 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_BASEAMOUNT_H_INCLUDED +#define RIPPLE_PROTOCOL_BASEAMOUNT_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +// Since `canonicalize` does not have access to a ledger, this is needed to put +// the low-level routine stAmountCanonicalize on an amendment switch. Only +// transactions need to use this switchover. Outside of a transaction it's safe +// to unconditionally use the new behavior. + +bool +getSTAmountCanonicalizeSwitchover(); + +void +setSTAmountCanonicalizeSwitchover(bool v); + +/** RAII class to set and restore the STAmount canonicalize switchover. + */ + +class STAmountSO +{ +public: + explicit STAmountSO(bool v) : saved_(getSTAmountCanonicalizeSwitchover()) + { + setSTAmountCanonicalizeSwitchover(v); + } + + ~STAmountSO() + { + setSTAmountCanonicalizeSwitchover(saved_); + } + +private: + bool saved_; +}; + +namespace BaseAmountConst { + +constexpr std::uint64_t tenTo14 = 100000000000000ull; +constexpr std::uint64_t tenTo14m1 = tenTo14 - 1; +constexpr std::uint64_t tenTo17 = tenTo14 * 1000; + +constexpr int cMinOffset = -96; +constexpr int cMaxOffset = 80; + +// Maximum native value supported by the code +constexpr std::uint64_t cMinValue = 1000000000000000ull; +constexpr std::uint64_t cMaxValue = 9999999999999999ull; +constexpr std::uint64_t cMaxNative = 9000000000000000000ull; + +// Max native value on network. +constexpr std::uint64_t cMaxNativeN = 100000000000000000ull; +constexpr std::uint64_t cIssuedCurrency = 0x8000000000000000ull; +constexpr std::uint64_t cPositive = 0x4000000000000000ull; +constexpr std::uint64_t cMPToken = 0x2000000000000000ull; +constexpr std::uint64_t cValueMask = ~(cPositive | cMPToken); + +} // namespace BaseAmountConst + +template +concept ValidAsset = std::is_same_v || std::is_same_v || + std::is_same_v || std::is_convertible_v || + std::is_convertible_v || std::is_convertible_v; + +// Internal form: +// 1: If amount is zero, then value is zero and offset is -100 +// 2: Otherwise: +// legal offset range is -96 to +80 inclusive +// value range is 10^15 to (10^16 - 1) inclusive +// amount = value * [10 ^ offset] + +// Wire form: +// High 8 bits are (offset+142), legal range is, 80 to 22 inclusive +// Low 56 bits are value, legal range is 10^15 to (10^16 - 1) inclusive +template +class BaseAmount +{ +public: + using mantissa_type = std::uint64_t; + using exponent_type = int; + using rep = std::pair; + +protected: + T mAsset; + mantissa_type mValue; + exponent_type mOffset; + bool mIsNative; // A shorthand for isXRP(mIssue). + bool mIsNegative; + +public: + static std::uint64_t const uRateOne; + + //-------------------------------------------------------------------------- + + struct unchecked + { + explicit unchecked() = default; + }; + + // Do not call canonicalize + BaseAmount( + T const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative, + unchecked); + + // Call canonicalize + BaseAmount( + T const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative); + + BaseAmount(std::int64_t mantissa); + + BaseAmount( + T const& asset, + std::uint64_t mantissa = 0, + int exponent = 0, + bool negative = false); + + explicit BaseAmount(std::uint64_t mantissa = 0, bool negative = false); + + explicit BaseAmount(BaseAmount const& amt); + + // VFALCO Is this needed when we have the previous signature? + BaseAmount( + T const& asset, + std::uint32_t mantissa, + int exponent = 0, + bool negative = false); + + BaseAmount(T const& asset, std::int64_t mantissa, int exponent = 0); + + BaseAmount(T const& asset, int mantissa, int exponent = 0); + + BaseAmount(IOUAmount const& amount, Issue const& issue); + BaseAmount(XRPAmount const& amount); + BaseAmount(MPTAmount const& amount, MPTIssue const& issue); + operator Number() const; + + //-------------------------------------------------------------------------- + // + // Observers + // + //-------------------------------------------------------------------------- + + int + exponent() const noexcept; + + std::string + getTypeName() const noexcept; + + bool + negative() const noexcept; + + std::uint64_t + mantissa() const noexcept; + + T const& + issue() const; + + AccountID const& + getIssuer() const; + + int + signum() const noexcept; + + BaseAmount const& + value() const noexcept; + + //-------------------------------------------------------------------------- + // + // Operators + // + //-------------------------------------------------------------------------- + + explicit operator bool() const noexcept; + + BaseAmount& + operator+=(BaseAmount const&); + BaseAmount& + operator-=(BaseAmount const&); + + BaseAmount& operator=(beast::Zero); + + BaseAmount& + operator=(XRPAmount const& amount); + + //-------------------------------------------------------------------------- + // + // Modification + // + //-------------------------------------------------------------------------- + + void + negate(); + + void + clear(); + + // Zero while copying currency and issuer. + void + clear(BaseAmount const& saTmpl); + + void + clear(T const& issue); + + void + setAsset(T const& a); + +protected: + void + set(std::int64_t v); + + void + canonicalize(); + + BaseAmount& + operator=(IOUAmount const& iou); + + friend BaseAmount + operator+(BaseAmount const& v1, BaseAmount const& v2); +}; + +template +bool +isNative(T const& asset) +{ + if constexpr (std::is_same_v) + return false; + else if constexpr (std::is_same_v) + return isXRP(asset); + else if constexpr (std::is_same_v) + return asset.isIssue() && isXRP(asset.issue()); + else + { + constexpr bool alwaysFalse = !std::is_same_v; + static_assert(alwaysFalse, "Unsupported type for isNative"); + } +} + +template +bool +isMPT(T const& asset) +{ + if constexpr (std::is_same_v) + return true; + else if constexpr (std::is_same_v) + return false; + else if constexpr (std::is_same_v) + return asset.isMPT(); + else + { + constexpr bool alwaysFalse = !std::is_same_v; + static_assert(alwaysFalse, "Unsupported type for isMPT"); + } +} + +template +std::int64_t +getSNValue(BaseAmount const& amount) +{ + if (!isNative(amount.issue())) + Throw("amount is not native!"); + + auto ret = static_cast(amount.mantissa()); + + assert(static_cast(ret) == amount.mantissa()); + + if (amount.negative()) + ret = -ret; + + return ret; +} + +template +std::int64_t +getMPTValue(BaseAmount const& amount) +{ + if (!isMPT(amount.issue())) + Throw("amount is not MPT!"); + + auto ret = static_cast(amount.mantissa()); + + assert(static_cast(ret) == amount.mantissa()); + + if (amount.negative()) + ret = -ret; + + return ret; +} + +template +std::uint64_t const BaseAmount::uRateOne = + getRate(BaseAmount(1), BaseAmount(1)); + +template +void +BaseAmount::setAsset(T const& asset) +{ + mAsset = asset; +} + +template +BaseAmount::BaseAmount( + T const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative, + unchecked) + : mValue(mantissa) + , mOffset(exponent) + , mIsNative(native) + , mIsNegative(negative) +{ + setAsset(asset, native); +} + +template +BaseAmount::BaseAmount( + T const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative) + : mValue(mantissa) + , mOffset(exponent) + , mIsNative(native) + , mIsNegative(negative) +{ + setAsset(asset, native); + canonicalize(); +} + +template +BaseAmount::BaseAmount( + T const& asset, + std::uint64_t mantissa, + int exponent, + bool negative) + : mAsset(asset), mValue(mantissa), mOffset(exponent), mIsNegative(negative) +{ + assert(mValue <= std::numeric_limits::max()); + canonicalize(); +} + +template +BaseAmount::BaseAmount(T const& asset, std::int64_t mantissa, int exponent) + : mAsset(asset), mOffset(exponent) +{ + set(mantissa); + canonicalize(); +} + +template +BaseAmount::BaseAmount( + T const& asset, + std::uint32_t mantissa, + int exponent, + bool negative) + : STAmount(asset, safe_cast(mantissa), exponent, negative) +{ +} + +template +BaseAmount::BaseAmount(T const& asset, int mantissa, int exponent) + : STAmount(asset, safe_cast(mantissa), exponent) +{ +} + +// Legacy support for new-style amounts +template +BaseAmount::BaseAmount(IOUAmount const& amount, Issue const& issue) + : mAsset(issue) + , mOffset(amount.exponent()) + , mIsNative(false) + , mIsNegative(amount < beast::zero) +{ + if (mIsNegative) + mValue = static_cast(-amount.mantissa()); + else + mValue = static_cast(amount.mantissa()); + + canonicalize(); +} + +template +BaseAmount::BaseAmount(MPTAmount const& amount, MPTIssue const& issue) + : mAsset(issue) + , mOffset(0) + , mIsNative(false) + , mIsNegative(amount < beast::zero) +{ + if (mIsNegative) + mValue = unsafe_cast(-amount.mpt()); + else + mValue = unsafe_cast(amount.mpt()); + + canonicalize(); +} + +template +BaseAmount::BaseAmount(std::int64_t mantissa) + : mAsset(xrpIssue()), mOffset(0), mIsNative(true) +{ + set(mantissa); +} + +template +BaseAmount::BaseAmount(std::uint64_t mantissa, bool negative) + : mAsset(xrpIssue()) + , mValue(mantissa) + , mOffset(0) + , mIsNative(true) + , mIsNegative(negative) +{ + assert(mValue <= std::numeric_limits::max()); +} + +template +BaseAmount::BaseAmount(BaseAmount const& from) + : mAsset(from.mAsset) + , mValue(from.mValue) + , mOffset(from.mOffset) + , mIsNegative(from.mIsNegative) +{ + assert(mValue <= std::numeric_limits::max()); + canonicalize(); +} + +template +BaseAmount::BaseAmount(XRPAmount const& amount) + : mAsset(xrpIssue()) + , mOffset(0) + , mIsNative(true) + , mIsNegative(amount < beast::zero) +{ + if (mIsNegative) + mValue = unsafe_cast(-amount.drops()); + else + mValue = unsafe_cast(amount.drops()); + + canonicalize(); +} + +//------------------------------------------------------------------------------ +// +// Observers +// +//------------------------------------------------------------------------------ + +template +int +BaseAmount::exponent() const noexcept +{ + return mOffset; +} + +template +bool +BaseAmount::negative() const noexcept +{ + return mIsNegative; +} + +template +std::uint64_t +BaseAmount::mantissa() const noexcept +{ + return mValue; +} + +template +T const& +BaseAmount::issue() const +{ + return mAsset.issue(); +} + +template +AccountID const& +BaseAmount::getIssuer() const +{ + return mAsset.issue().account; +} + +template +int +BaseAmount::signum() const noexcept +{ + return mValue ? (mIsNegative ? -1 : 1) : 0; +} + +template +BaseAmount::operator bool() const noexcept +{ + return *this != beast::zero; +} + +template +BaseAmount& +BaseAmount::operator=(beast::Zero) +{ + clear(); + return *this; +} + +template +BaseAmount& +BaseAmount::operator=(XRPAmount const& amount) +{ + *this = BaseAmount(amount); + return *this; +} + +template +BaseAmount& +BaseAmount::operator=(IOUAmount const& iou) +{ + assert(mIsNative == false); + mOffset = iou.exponent(); + mIsNegative = iou < beast::zero; + if (mIsNegative) + mValue = static_cast(-iou.mantissa()); + else + mValue = static_cast(iou.mantissa()); + return *this; +} + +template +BaseAmount& +BaseAmount::operator+=(BaseAmount const& a) +{ + *this = *this + a; + return *this; +} + +template +BaseAmount& +BaseAmount::operator-=(BaseAmount const& a) +{ + *this = *this - a; + return *this; +} + +template +void +BaseAmount::negate() +{ + if (*this != beast::zero) + mIsNegative = !mIsNegative; +} + +template +void +BaseAmount::clear() +{ + // The -100 is used to allow 0 to sort less than a small positive values + // which have a negative exponent. + mOffset = mIsNative ? 0 : -100; + mValue = 0; + mIsNegative = false; +} + +// Zero while copying currency and issuer. +template +void +BaseAmount::clear(BaseAmount const& amnt) +{ + clear(amnt.issue()); +} + +template +BaseAmount const& +BaseAmount::value() const noexcept +{ + return *this; +} + +// amount = mValue * [10 ^ mOffset] +// Representation range is 10^80 - 10^(-80). +// +// On the wire: +// - high bit is 0 for XRP, 1 for issued currency +// - next bit is 1 for positive, 0 for negative (except 0 issued currency, which +// is a special case of 0x8000000000000000 +// - for issued currencies, the next 8 bits are (mOffset+97). +// The +97 is so that this value is always positive. +// - The remaining bits are significant digits (mantissa) +// That's 54 bits for issued currency and 62 bits for native +// (but XRP only needs 57 bits for the max value of 10^17 drops) +// +// mValue is zero if the amount is zero, otherwise it's within the range +// 10^15 to (10^16 - 1) inclusive. +// mOffset is in the range -96 to +80. +template +void +BaseAmount::canonicalize() +{ + if (isXRP(*this) || isMPT(mAsset)) + { + // native currency amounts should always have an offset of zero + mIsNative = isXRP(*this); + + // log(2^64,10) ~ 19.2 + if (mValue == 0 || mOffset <= -20) + { + mValue = 0; + mOffset = 0; + mIsNegative = false; + return; + } + + if (getSTAmountCanonicalizeSwitchover()) + { + // log(cMaxNativeN, 10) == 17 + if (mOffset > 17) + Throw( + "Native currency amount out of range"); + } + + if (getSTNumberSwitchover() && getSTAmountCanonicalizeSwitchover()) + { + Number num( + mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{}); + if (mIsNative) + { + XRPAmount xrp{num}; + mIsNegative = xrp.drops() < 0; + mValue = mIsNegative ? -xrp.drops() : xrp.drops(); + } + else + { + MPTAmount c{num}; + mIsNegative = c.mpt() < 0; + mValue = mIsNegative ? -c.mpt() : c.mpt(); + } + mOffset = 0; + } + else + { + while (mOffset < 0) + { + mValue /= 10; + ++mOffset; + } + + while (mOffset > 0) + { + if (getSTAmountCanonicalizeSwitchover()) + { + // N.B. do not move the overflow check to after the + // multiplication + if (mValue > BaseAmountConst::cMaxNativeN) + Throw( + "Native currency amount out of range"); + } + mValue *= 10; + --mOffset; + } + } + + if (mValue > BaseAmountConst::cMaxNativeN) + Throw("Native currency amount out of range"); + + return; + } + + mIsNative = false; + + if (getSTNumberSwitchover()) + { + auto mantissa = static_cast(mValue); + auto exponent = mOffset; + + if (mIsNegative) + mantissa = -mantissa; + return; + } + + if (mValue == 0) + { + mOffset = -100; + mIsNegative = false; + return; + } + + while ((mValue < BaseAmountConst::cMinValue) && + (mOffset > BaseAmountConst::cMinOffset)) + { + mValue *= 10; + --mOffset; + } + + while (mValue > BaseAmountConst::cMaxValue) + { + if (mOffset >= BaseAmountConst::cMaxOffset) + Throw("value overflow"); + + mValue /= 10; + ++mOffset; + } + + if ((mOffset < BaseAmountConst::cMinOffset) || + (mValue < BaseAmountConst::cMinValue)) + { + mValue = 0; + mIsNegative = false; + mOffset = -100; + return; + } + + if (mOffset > BaseAmountConst::cMaxOffset) + Throw("value overflow"); + + assert( + (mValue == 0) || + ((mValue >= BaseAmountConst::cMinValue) && + (mValue <= BaseAmountConst::cMaxValue))); + assert( + (mValue == 0) || + ((mOffset >= BaseAmountConst::cMinOffset) && + (mOffset <= BaseAmountConst::cMaxOffset))); + assert((mValue != 0) || (mOffset != -100)); +} + +template +void +BaseAmount::set(std::int64_t v) +{ + if (v < 0) + { + mIsNegative = true; + mValue = static_cast(-v); + } + else + { + mIsNegative = false; + mValue = static_cast(v); + } +} + +//------------------------------------------------------------------------------ +// +// Operators +// +//------------------------------------------------------------------------------ +namespace detail { + +template +bool +areComparable(BaseAmount const& v1, BaseAmount const& v2) +{ + return (v1.isMPT() && v2.isMPT() && + v1.asset().mptIssue() == v2.asset().mptIssue()) || + (v1.isIssue() && v1.native() == v2.native() && + v1.issue().currency == v2.issue().currency); +} + +void +canonicalizeRound(bool nativeOrMPT, std::uint64_t& value, int& offset, bool); + +void +canonicalizeRoundStrict( + bool nativeOrMPT, + std::uint64_t& value, + int& offset, + bool roundUp); + +// We need a class that has an interface similar to NumberRoundModeGuard +// but does nothing. +class DontAffectNumberRoundMode +{ +public: + explicit DontAffectNumberRoundMode(Number::rounding_mode mode) noexcept + { + } + + DontAffectNumberRoundMode(DontAffectNumberRoundMode const&) = delete; + + DontAffectNumberRoundMode& + operator=(DontAffectNumberRoundMode const&) = delete; +}; + +inline static std::uint64_t +muldiv( + std::uint64_t multiplier, + std::uint64_t multiplicand, + std::uint64_t divisor) +{ + boost::multiprecision::uint128_t ret; + + boost::multiprecision::multiply(ret, multiplier, multiplicand); + ret /= divisor; + + if (ret > std::numeric_limits::max()) + { + Throw( + "overflow: (" + std::to_string(multiplier) + " * " + + std::to_string(multiplicand) + ") / " + std::to_string(divisor)); + } + + return static_cast(ret); +} + +inline static std::uint64_t +muldiv_round( + std::uint64_t multiplier, + std::uint64_t multiplicand, + std::uint64_t divisor, + std::uint64_t rounding) +{ + boost::multiprecision::uint128_t ret; + + boost::multiprecision::multiply(ret, multiplier, multiplicand); + ret += rounding; + ret /= divisor; + + if (ret > std::numeric_limits::max()) + { + Throw( + "overflow: ((" + std::to_string(multiplier) + " * " + + std::to_string(multiplicand) + ") + " + std::to_string(rounding) + + ") / " + std::to_string(divisor)); + } + + return static_cast(ret); +} + +// Pass the canonicalizeRound function pointer as a template parameter. +// +// We might need to use NumberRoundModeGuard. Allow the caller +// to pass either that or a replacement as a template parameter. +template < + void (*CanonicalizeFunc)(bool, std::uint64_t&, int&, bool), + typename MightSaveRound, + ValidAsset T> +inline static BaseAmount +mulRoundImpl( + BaseAmount const& v1, + BaseAmount const& v2, + T const& asset, + bool roundUp) +{ + if (v1 == beast::zero || v2 == beast::zero) + return {asset}; + + bool const xrp = isNative(asset); + + // TODO MPT + if (isNative(v1) && isNative(v2) && isNative(asset)) + { + std::uint64_t minV = + (getSNValue(v1) < getSNValue(v2)) ? getSNValue(v1) : getSNValue(v2); + std::uint64_t maxV = + (getSNValue(v1) < getSNValue(v2)) ? getSNValue(v2) : getSNValue(v1); + + if (minV > 3000000000ull) // sqrt(cMaxNative) + Throw("Native value overflow"); + + if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 + Throw("Native value overflow"); + + return BaseAmount(minV * maxV); + } + // TODO MPT + if (isMPT(v1) && isMPT(v2) && isMPT(asset)) + { + std::uint64_t minV = (getMPTValue(v1) < getMPTValue(v2)) + ? getMPTValue(v1) + : getMPTValue(v2); + std::uint64_t maxV = (getMPTValue(v1) < getMPTValue(v2)) + ? getMPTValue(v2) + : getMPTValue(v1); + + if (minV > 3000000000ull) // sqrt(cMaxNative) + Throw("Asset value overflow"); + + if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 + Throw("Asset value overflow"); + + return BaseAmount(asset, minV * maxV); + } + + std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa(); + int offset1 = v1.exponent(), offset2 = v2.exponent(); + + // TODO MPT + if (isNative(v1) || isMPT(v1)) + { + while (value1 < BaseAmountConst::cMinValue) + { + value1 *= 10; + --offset1; + } + } + + // TODO MPT + if (isNative(v2) || isMPT(v2)) + { + while (value2 < BaseAmountConst::cMinValue) + { + value2 *= 10; + --offset2; + } + } + + bool const resultNegative = v1.negative() != v2.negative(); + + // We multiply the two mantissas (each is between 10^15 + // and 10^16), so their product is in the 10^30 to 10^32 + // range. Dividing their product by 10^14 maintains the + // precision, by scaling the result to 10^16 to 10^18. + // + // If the we're rounding up, we want to round up away + // from zero, and if we're rounding down, truncation + // is implicit. + std::uint64_t amount = detail::muldiv_round( + value1, + value2, + BaseAmountConst::tenTo14, + (resultNegative != roundUp) ? BaseAmountConst::tenTo14m1 : 0); + + int offset = offset1 + offset2 + 14; + if (resultNegative != roundUp) + { + CanonicalizeFunc(xrp, amount, offset, roundUp); + } + BaseAmount result = [&]() { + // If appropriate, tell Number to round down. This gives the desired + // result from STAmount::canonicalize. + MightSaveRound const savedRound(Number::towards_zero); + return BaseAmount(asset, amount, offset, resultNegative); + }(); + + if (roundUp && !resultNegative && !result) + { + if (xrp) + { + // return the smallest value above zero + amount = 1; + offset = 0; + } + else + { + // return the smallest value above zero + amount = BaseAmountConst::cMinValue; + offset = BaseAmountConst::cMinOffset; + } + return BaseAmount(asset, amount, offset, resultNegative); + } + return result; +} + +// We might need to use NumberRoundModeGuard. Allow the caller +// to pass either that or a replacement as a template parameter. +template +static BaseAmount +divRoundImpl( + BaseAmount const& num, + BaseAmount const& den, + Asset const& asset, + bool roundUp) +{ + if (den == beast::zero) + Throw("division by zero"); + + if (num == beast::zero) + return {asset}; + + std::uint64_t numVal = num.mantissa(), denVal = den.mantissa(); + int numOffset = num.exponent(), denOffset = den.exponent(); + + // TODO MPT + if (isNative(num) || isMPT(num)) + { + while (numVal < BaseAmountConst::cMinValue) + { + numVal *= 10; + --numOffset; + } + } + + // TODO MPT + if (isNative(den) || isMPT(den)) + { + while (denVal < BaseAmountConst::cMinValue) + { + denVal *= 10; + --denOffset; + } + } + + bool const resultNegative = (num.negative() != den.negative()); + + // We divide the two mantissas (each is between 10^15 + // and 10^16). To maintain precision, we multiply the + // numerator by 10^17 (the product is in the range of + // 10^32 to 10^33) followed by a division, so the result + // is in the range of 10^16 to 10^15. + // + // We round away from zero if we're rounding up or + // truncate if we're rounding down. + std::uint64_t amount = detail::muldiv_round( + numVal, + BaseAmountConst::tenTo17, + denVal, + (resultNegative != roundUp) ? denVal - 1 : 0); + + int offset = numOffset - denOffset - 17; + + // TODO MPT + if (resultNegative != roundUp) + canonicalizeRound( + isNative(asset) || isMPT(asset), amount, offset, roundUp); + + BaseAmount result = [&]() { + // If appropriate, tell Number the rounding mode we are using. + // Note that "roundUp == true" actually means "round away from zero". + // Otherwise round toward zero. + using enum Number::rounding_mode; + MightSaveRound const savedRound( + roundUp ^ resultNegative ? upward : downward); + return BaseAmount(asset, amount, offset, resultNegative); + }(); + + if (roundUp && !resultNegative && !result) + { + if (isNative(asset) || isMPT(asset)) + { + // return the smallest value above zero + amount = 1; + offset = 0; + } + else + { + // return the smallest value above zero + amount = BaseAmountConst::cMinValue; + offset = BaseAmountConst::cMinOffset; + } + return BaseAmount(asset, amount, offset, resultNegative); + } + return result; +} + +} // namespace detail + +template +bool +operator==(BaseAmount const& lhs, BaseAmount const& rhs) +{ + return areComparable(lhs, rhs) && lhs.negative() == rhs.negative() && + lhs.exponent() == rhs.exponent() && lhs.mantissa() == rhs.mantissa(); +} + +template +bool +operator<(BaseAmount const& lhs, BaseAmount const& rhs) +{ + if (!areComparable(lhs, rhs)) + Throw( + "Can't compare amounts that are't comparable!"); + + if (lhs.negative() != rhs.negative()) + return lhs.negative(); + + if (lhs.mantissa() == 0) + { + if (rhs.negative()) + return false; + return rhs.mantissa() != 0; + } + + // We know that lhs is non-zero and both sides have the same sign. Since + // rhs is zero (and thus not negative), lhs must, therefore, be strictly + // greater than zero. So if rhs is zero, the comparison must be false. + if (rhs.mantissa() == 0) + return false; + + if (lhs.exponent() > rhs.exponent()) + return lhs.negative(); + if (lhs.exponent() < rhs.exponent()) + return !lhs.negative(); + if (lhs.mantissa() > rhs.mantissa()) + return lhs.negative(); + if (lhs.mantissa() < rhs.mantissa()) + return !lhs.negative(); + + return false; +} + +template +bool +operator!=(BaseAmount const& lhs, BaseAmount const& rhs) +{ + return !(lhs == rhs); +} + +template +bool +operator>(BaseAmount const& lhs, BaseAmount const& rhs) +{ + return rhs < lhs; +} + +template +bool +operator<=(BaseAmount const& lhs, BaseAmount const& rhs) +{ + return !(rhs < lhs); +} + +template +bool +operator>=(BaseAmount const& lhs, BaseAmount const& rhs) +{ + return !(lhs < rhs); +} + +template +BaseAmount +operator-(BaseAmount const& value) +{ + if (value.mantissa() == 0) + return value; + return BaseAmount( + value.issue(), + value.mantissa(), + value.exponent(), + value.native(), + !value.negative(), + BaseAmount::unchecked()); +} + +//------------------------------------------------------------------------------ +// +// Arithmetic +// +//------------------------------------------------------------------------------ + +template +BaseAmount +operator+(BaseAmount const& v1, BaseAmount const& v2) +{ + if (!areComparable(v1, v2)) + Throw("Can't add amounts that are't comparable!"); + + if (v2 == beast::zero) + return v1; + + if (v1 == beast::zero) + { + // Result must be in terms of v1 currency and issuer. + return BaseAmount{ + v1.asset(), v2.mantissa(), v2.exponent(), v2.negative()}; + } + + // TODO + if (isNative(v1.issue())) + return {v1.getFName(), getSNValue(v1) + getSNValue(v2)}; + if (isMPT(v1.issue())) + return {v1.mAsset, v1.mpt().mpt() + v2.mpt().mpt()}; + + if (getSTNumberSwitchover()) + { + auto x = v1; + x = v1.iou() + v2.iou(); + return x; + } + + int ov1 = v1.exponent(), ov2 = v2.exponent(); + std::int64_t vv1 = static_cast(v1.mantissa()); + std::int64_t vv2 = static_cast(v2.mantissa()); + + if (v1.negative()) + vv1 = -vv1; + + if (v2.negative()) + vv2 = -vv2; + + while (ov1 < ov2) + { + vv1 /= 10; + ++ov1; + } + + while (ov2 < ov1) + { + vv2 /= 10; + ++ov2; + } + + // This addition cannot overflow an std::int64_t. It can overflow an + // STAmount and the constructor will throw. + + std::int64_t fv = vv1 + vv2; + + if ((fv >= -10) && (fv <= 10)) + return {v1.getFName(), v1.asset()}; + + if (fv >= 0) + return BaseAmount{ + v1.asset(), static_cast(fv), ov1, false}; + + return BaseAmount{ + v1.asset(), static_cast(-fv), ov1, true}; +} + +template +BaseAmount +operator-(BaseAmount const& v1, BaseAmount const& v2) +{ + return v1 + (-v2); +} + +template +BaseAmount +divide(BaseAmount const& num, BaseAmount const& den, T const& asset) +{ + if (den == beast::zero) + Throw("division by zero"); + + if (num == beast::zero) + return {asset}; + + std::uint64_t numVal = num.mantissa(); + std::uint64_t denVal = den.mantissa(); + int numOffset = num.exponent(); + int denOffset = den.exponent(); + + if (isNative(num) || isMPT(num)) + { + while (numVal < BaseAmountConst::cMinValue) + { + // Need to bring into range + numVal *= 10; + --numOffset; + } + } + + if (isNative(den) || isMPT(den)) + { + while (denVal < BaseAmountConst::cMinValue) + { + denVal *= 10; + --denOffset; + } + } + + // We divide the two mantissas (each is between 10^15 + // and 10^16). To maintain precision, we multiply the + // numerator by 10^17 (the product is in the range of + // 10^32 to 10^33) followed by a division, so the result + // is in the range of 10^16 to 10^15. + return BaseAmount( + asset, + detail::muldiv(numVal, BaseAmountConst::tenTo17, denVal) + 5, + numOffset - denOffset - 17, + num.negative() != den.negative()); +} + +template +BaseAmount +multiply(BaseAmount const& v1, BaseAmount const& v2, T const& asset) +{ + if (v1 == beast::zero || v2 == beast::zero) + return BaseAmount(asset); + + if (isNative(v1) && isNative(v2) && isNative(asset)) + { + std::uint64_t const minV = + getSNValue(v1) < getSNValue(v2) ? getSNValue(v1) : getSNValue(v2); + std::uint64_t const maxV = + getSNValue(v1) < getSNValue(v2) ? getSNValue(v2) : getSNValue(v1); + + if (minV > 3000000000ull) // sqrt(cMaxNative) + Throw("Native value overflow"); + + if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 + Throw("Native value overflow"); + + return STAmount(v1.getFName(), minV * maxV); + } + if (isMPT(v1) && isMPT(v2) && isMPT(asset)) + { + std::uint64_t const minV = getMPTValue(v1) < getMPTValue(v2) + ? getMPTValue(v1) + : getMPTValue(v2); + std::uint64_t const maxV = getMPTValue(v1) < getMPTValue(v2) + ? getMPTValue(v2) + : getMPTValue(v1); + + if (minV > 3000000000ull) // sqrt(cMaxNative) + Throw("Asset value overflow"); + + if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 + Throw("Asset value overflow"); + + return STAmount(asset, minV * maxV); + } + + if (getSTNumberSwitchover()) + return {IOUAmount{Number{v1} * Number{v2}}, asset}; + + std::uint64_t value1 = v1.mantissa(); + std::uint64_t value2 = v2.mantissa(); + int offset1 = v1.exponent(); + int offset2 = v2.exponent(); + + // TODO MPT + if (isNative(v1) || isMPT(v1)) + { + while (value1 < BaseAmountConst::cMinValue) + { + value1 *= 10; + --offset1; + } + } + + if (isNative(v2) || isMPT(v2)) + { + while (value2 < BaseAmountConst::cMinValue) + { + value2 *= 10; + --offset2; + } + } + + // We multiply the two mantissas (each is between 10^15 + // and 10^16), so their product is in the 10^30 to 10^32 + // range. Dividing their product by 10^14 maintains the + // precision, by scaling the result to 10^16 to 10^18. + return BaseAmount( + asset, + detail::muldiv(value1, value2, BaseAmountConst::tenTo14) + 7, + offset1 + offset2 + 14, + v1.negative() != v2.negative()); +} + +// multiply rounding result in specified direction +template +BaseAmount +mulRound( + BaseAmount const& v1, + BaseAmount const& v2, + T const& asset, + bool roundUp) +{ + return detail::mulRoundImpl< + detail::canonicalizeRound, + detail::DontAffectNumberRoundMode, + T>(v1, v2, asset, roundUp); +} + +// multiply following the rounding directions more precisely. +template +BaseAmount +mulRoundStrict( + BaseAmount const& v1, + BaseAmount const& v2, + T const& asset, + bool roundUp) +{ + return detail:: + mulRoundImpl( + v1, v2, asset, roundUp); +} + +// divide rounding result in specified direction +template +BaseAmount +divRound( + BaseAmount const& num, + BaseAmount const& den, + T const& asset, + bool roundUp) +{ + return divRoundImpl( + num, den, asset, roundUp); +} + +// divide following the rounding directions more precisely. +template +BaseAmount +divRoundStrict( + BaseAmount const& num, + BaseAmount const& den, + T const& asset, + bool roundUp) +{ + return divRoundImpl(num, den, asset, roundUp); +} + +// Someone is offering X for Y, what is the rate? +// Rate: smaller is better, the taker wants the most out: in/out +// 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. +template +std::uint64_t +getRate(BaseAmount const& offerOut, BaseAmount const& offerIn) +{ + if (offerOut == beast::zero) + return 0; + try + { + auto const r = divide(offerIn, offerOut, noIssue()); + if (r == beast::zero) // offer is too good + return 0; + assert((r.exponent() >= -100) && (r.exponent() <= 155)); + std::uint64_t ret = r.exponent() + 100; + return (ret << (64 - 8)) | r.mantissa(); + } + catch (std::exception const&) + { + } + + // overflow -- very bad offer + return 0; +} + +//------------------------------------------------------------------------------ + +template +bool +isXRP(BaseAmount const& amount) +{ + return isNative(amount.issue()); +} + +template +bool +isMPT(BaseAmount const& amount) +{ + return isMPT(amount.issue()); +} + +} // namespace ripple + +#endif diff --git a/src/ripple/protocol/STAmount.h b/src/ripple/protocol/STAmount.h index 641083f02a6..807cde49408 100644 --- a/src/ripple/protocol/STAmount.h +++ b/src/ripple/protocol/STAmount.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -794,7 +795,7 @@ isMPT(STAmount const& amount) // the low-level routine stAmountCanonicalize on an amendment switch. Only // transactions need to use this switchover. Outside of a transaction it's safe // to unconditionally use the new behavior. - +#if 0 bool getSTAmountCanonicalizeSwitchover(); @@ -820,6 +821,7 @@ class STAmountSO private: bool saved_; }; +#endif } // namespace ripple diff --git a/src/ripple/protocol/impl/BaseAmount.cpp b/src/ripple/protocol/impl/BaseAmount.cpp new file mode 100644 index 00000000000..6bd7c28e628 --- /dev/null +++ b/src/ripple/protocol/impl/BaseAmount.cpp @@ -0,0 +1,161 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#include + +namespace ripple { + +namespace { + +// Use a static inside a function to help prevent order-of-initialzation issues +LocalValue& +getStaticSTAmountCanonicalizeSwitchover() +{ + static LocalValue r{true}; + return r; +} +} // namespace + +bool +getSTAmountCanonicalizeSwitchover() +{ + return *getStaticSTAmountCanonicalizeSwitchover(); +} + +void +setSTAmountCanonicalizeSwitchover(bool v) +{ + *getStaticSTAmountCanonicalizeSwitchover() = v; +} + +//------------------------------------------------------------------------------ +// +// Arithmetic +// +//------------------------------------------------------------------------------ + +// Calculate (a * b) / c when all three values are 64-bit +// without loss of precision: + +// This is the legacy version of canonicalizeRound. It's been in use +// for years, so it is deeply embedded in the behavior of cross-currency +// transactions. +// +// However in 2022 it was noticed that the rounding characteristics were +// surprising. When the code converts from IOU-like to XRP-like there may +// be a fraction of the IOU-like representation that is too small to be +// represented in drops. `canonicalizeRound()` currently does some unusual +// rounding. +// +// 1. If the fractional part is greater than or equal to 0.1, then the +// number of drops is rounded up. +// +// 2. However, if the fractional part is less than 0.1 (for example, +// 0.099999), then the number of drops is rounded down. +// +// The XRP Ledger has this rounding behavior baked in. But there are +// situations where this rounding behavior led to undesirable outcomes. +// So an alternative rounding approach was introduced. You'll see that +// alternative below. +namespace detail { + +void +canonicalizeRound(bool nativeOrMPT, std::uint64_t& value, int& offset, bool) +{ + if (nativeOrMPT) + { + if (offset < 0) + { + int loops = 0; + + while (offset < -1) + { + value /= 10; + ++offset; + ++loops; + } + + value += (loops >= 2) ? 9 : 10; // add before last divide + value /= 10; + ++offset; + } + } + else if (value > BaseAmountConst::cMaxValue) + { + while (value > (10 * BaseAmountConst::cMaxValue)) + { + value /= 10; + ++offset; + } + + value += 9; // add before last divide + value /= 10; + ++offset; + } +} + +// The original canonicalizeRound did not allow the rounding direction to +// be specified. It also ignored some of the bits that could contribute to +// rounding decisions. canonicalizeRoundStrict() tracks all of the bits in +// the value being rounded. +void +canonicalizeRoundStrict( + bool nativeOrMPT, + std::uint64_t& value, + int& offset, + bool roundUp) +{ + if (nativeOrMPT) + { + if (offset < 0) + { + bool hadRemainder = false; + + while (offset < -1) + { + // It would be better to use std::lldiv than to separately + // compute the remainder. But std::lldiv does not support + // unsigned arguments. + std::uint64_t const newValue = value / 10; + hadRemainder |= (value != (newValue * 10)); + value = newValue; + ++offset; + } + value += + (hadRemainder && roundUp) ? 10 : 9; // Add before last divide + value /= 10; + ++offset; + } + } + else if (value > BaseAmountConst::cMaxValue) + { + while (value > (10 * BaseAmountConst::cMaxValue)) + { + value /= 10; + ++offset; + } + value += 9; // add before last divide + value /= 10; + ++offset; + } +} + +} // namespace detail + +} // namespace ripple diff --git a/src/ripple/protocol/impl/STAmount.cpp b/src/ripple/protocol/impl/STAmount.cpp index 121a89278a6..d4d0849a529 100644 --- a/src/ripple/protocol/impl/STAmount.cpp +++ b/src/ripple/protocol/impl/STAmount.cpp @@ -35,6 +35,7 @@ namespace ripple { +#if 0 namespace { // Use a static inside a function to help prevent order-of-initialzation issues @@ -57,6 +58,7 @@ setSTAmountCanonicalizeSwitchover(bool v) { *getStaticSTAmountCanonicalizeSwitchover() = v; } +#endif static const std::uint64_t tenTo14 = 100000000000000ull; static const std::uint64_t tenTo14m1 = tenTo14 - 1; @@ -435,7 +437,7 @@ STAmount::mpt() const if (!isMPT()) Throw("Cannot return STAmount as MPTAmount"); - auto value = static_cast(mValue); + auto value = static_cast(mValue); if (mIsNegative) value = -value;