#ifndef XRPL_PROTOCOL_STAMOUNT_H_INCLUDED #define XRPL_PROTOCOL_STAMOUNT_H_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ripple { // 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 class STAmount final : public STBase, public CountedObject { public: using mantissa_type = std::uint64_t; using exponent_type = int; using rep = std::pair; private: Asset mAsset; mantissa_type mValue; exponent_type mOffset; bool mIsNegative; // The Enforce integer setting is not stored or serialized. If set, it is // used during automatic conversions to Number. If not set, the default // behavior is used. It can also be overridden when coverting by using // toNumber(). std::optional enforceConversion_; public: using value_type = STAmount; static int const cMinOffset = -96; static int const cMaxOffset = 80; // Maximum native value supported by the code static std::uint64_t const cMinValue = 1000000000000000ull; static std::uint64_t const cMaxValue = 9999999999999999ull; static std::uint64_t const cMaxNative = 9000000000000000000ull; // Max native value on network. static std::uint64_t const cMaxNativeN = 100000000000000000ull; static std::uint64_t const cIssuedCurrency = 0x8000000000000000ull; static std::uint64_t const cPositive = 0x4000000000000000ull; static std::uint64_t const cMPToken = 0x2000000000000000ull; static std::uint64_t const cValueMask = ~(cPositive | cMPToken); static std::uint64_t const uRateOne; //-------------------------------------------------------------------------- STAmount(SerialIter& sit, SField const& name); struct unchecked { explicit unchecked() = default; }; // Do not call canonicalize template STAmount( SField const& name, A const& asset, mantissa_type mantissa, exponent_type exponent, bool negative, unchecked); template STAmount( A const& asset, mantissa_type mantissa, exponent_type exponent, bool negative, unchecked); // Call canonicalize template STAmount( SField const& name, A const& asset, mantissa_type mantissa = 0, exponent_type exponent = 0, bool negative = false); STAmount(SField const& name, std::int64_t mantissa); STAmount( SField const& name, std::uint64_t mantissa = 0, bool negative = false); explicit STAmount(std::uint64_t mantissa = 0, bool negative = false); explicit STAmount(SField const& name, STAmount const& amt); template STAmount( A const& asset, std::uint64_t mantissa = 0, int exponent = 0, bool negative = false) : mAsset(asset) , mValue(mantissa) , mOffset(exponent) , mIsNegative(negative) { canonicalize(); } // VFALCO Is this needed when we have the previous signature? template STAmount( A const& asset, std::uint32_t mantissa, int exponent = 0, bool negative = false); template STAmount(A const& asset, std::int64_t mantissa, int exponent = 0); template STAmount(A const& asset, int mantissa, int exponent = 0); template STAmount( A const& asset, Number const& number, std::optional enforce = std::nullopt) : STAmount(asset, number.mantissa(), number.exponent()) { enforceConversion_ = enforce; if (!enforce) { // Use the default conversion behavior [[maybe_unused]] Number const n = *this; } else if (enforce == Number::strong) { // Throw if it's not valid if (!validNumber()) { Throw( "STAmount::STAmount integer Number lost precision"); } } } // Legacy support for new-style amounts STAmount(IOUAmount const& amount, Issue const& issue); STAmount(XRPAmount const& amount); STAmount(MPTAmount const& amount, MPTIssue const& mptIssue); operator Number() const; Number toNumber(Number::EnforceInteger enforce) const; void setIntegerEnforcement(std::optional enforce); std::optional integerEnforcement() const noexcept; bool validNumber() const noexcept; //-------------------------------------------------------------------------- // // Observers // //-------------------------------------------------------------------------- int exponent() const noexcept; bool integral() const noexcept; bool native() const noexcept; template constexpr bool holds() const noexcept; bool negative() const noexcept; std::uint64_t mantissa() const noexcept; Asset const& asset() const; template constexpr TIss const& get() const; Issue const& issue() const; // These three are deprecated Currency const& getCurrency() const; AccountID const& getIssuer() const; int signum() const noexcept; /** Returns a zero value with the same issuer and currency. */ STAmount zeroed() const; void setJson(Json::Value&) const; STAmount const& value() const noexcept; //-------------------------------------------------------------------------- // // Operators // //-------------------------------------------------------------------------- explicit operator bool() const noexcept; STAmount& operator+=(STAmount const&); STAmount& operator-=(STAmount const&); STAmount& operator=(beast::Zero); STAmount& operator=(XRPAmount const& amount); STAmount& operator=(Number const&); //-------------------------------------------------------------------------- // // Modification // //-------------------------------------------------------------------------- void negate(); void clear(); // Zero while copying currency and issuer. void clear(Asset const& asset); void setIssuer(AccountID const& uIssuer); /** Set the Issue for this amount. */ void setIssue(Asset const& asset); //-------------------------------------------------------------------------- // // STBase // //-------------------------------------------------------------------------- SerializedTypeID getSType() const override; std::string getFullText() const override; std::string getText() const override; Json::Value getJson(JsonOptions = JsonOptions::none) const override; void add(Serializer& s) const override; bool isEquivalent(STBase const& t) const override; bool isDefault() const override; XRPAmount xrp() const; IOUAmount iou() const; MPTAmount mpt() const; private: static std::unique_ptr construct(SerialIter&, SField const& name); void set(std::int64_t v); void canonicalize(); STBase* copy(std::size_t n, void* buf) const override; STBase* move(std::size_t n, void* buf) override; STAmount& operator=(IOUAmount const& iou); friend class detail::STVar; friend STAmount operator+(STAmount const& v1, STAmount const& v2); }; template STAmount::STAmount( SField const& name, A const& asset, mantissa_type mantissa, exponent_type exponent, bool negative, unchecked) : STBase(name) , mAsset(asset) , mValue(mantissa) , mOffset(exponent) , mIsNegative(negative) { } template STAmount::STAmount( A const& asset, mantissa_type mantissa, exponent_type exponent, bool negative, unchecked) : mAsset(asset), mValue(mantissa), mOffset(exponent), mIsNegative(negative) { } template STAmount::STAmount( SField const& name, A const& asset, std::uint64_t mantissa, int exponent, bool negative) : STBase(name) , mAsset(asset) , mValue(mantissa) , mOffset(exponent) , mIsNegative(negative) { // mValue is uint64, but needs to fit in the range of int64 XRPL_ASSERT( mValue <= std::numeric_limits::max(), "ripple::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : " "maximum mantissa input"); canonicalize(); } template STAmount::STAmount(A const& asset, std::int64_t mantissa, int exponent) : mAsset(asset), mOffset(exponent) { set(mantissa); canonicalize(); } template STAmount::STAmount( A const& asset, std::uint32_t mantissa, int exponent, bool negative) : STAmount(asset, safe_cast(mantissa), exponent, negative) { } template STAmount::STAmount(A const& asset, int mantissa, int exponent) : STAmount(asset, safe_cast(mantissa), exponent) { } // Legacy support for new-style amounts inline STAmount::STAmount(IOUAmount const& amount, Issue const& issue) : mAsset(issue) , mOffset(amount.exponent()) , mIsNegative(amount < beast::zero) { if (mIsNegative) mValue = unsafe_cast(-amount.mantissa()); else mValue = unsafe_cast(amount.mantissa()); canonicalize(); } inline STAmount::STAmount(MPTAmount const& amount, MPTIssue const& mptIssue) : mAsset(mptIssue), mOffset(0), mIsNegative(amount < beast::zero) { if (mIsNegative) mValue = unsafe_cast(-amount.value()); else mValue = unsafe_cast(amount.value()); canonicalize(); } //------------------------------------------------------------------------------ // // Creation // //------------------------------------------------------------------------------ // VFALCO TODO The parameter type should be Quality not uint64_t STAmount amountFromQuality(std::uint64_t rate); STAmount amountFromString(Asset const& asset, std::string const& amount); STAmount 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 // //------------------------------------------------------------------------------ inline int STAmount::exponent() const noexcept { return mOffset; } inline bool STAmount::integral() const noexcept { return mAsset.integral(); } inline bool STAmount::native() const noexcept { return mAsset.native(); } template constexpr bool STAmount::holds() const noexcept { return mAsset.holds(); } inline bool STAmount::negative() const noexcept { return mIsNegative; } inline std::uint64_t STAmount::mantissa() const noexcept { return mValue; } inline Asset const& STAmount::asset() const { return mAsset; } template constexpr TIss const& STAmount::get() const { return mAsset.get(); } inline Issue const& STAmount::issue() const { return get(); } inline Currency const& STAmount::getCurrency() const { return mAsset.get().currency; } inline AccountID const& STAmount::getIssuer() const { return mAsset.getIssuer(); } inline int STAmount::signum() const noexcept { return mValue ? (mIsNegative ? -1 : 1) : 0; } inline STAmount STAmount::zeroed() const { return STAmount(mAsset); } inline STAmount::operator bool() const noexcept { return *this != beast::zero; } inline STAmount::operator Number() const { if (enforceConversion_) return toNumber(*enforceConversion_); if (native()) return xrp(); if (mAsset.holds()) return mpt(); return iou(); } inline Number STAmount::toNumber(Number::EnforceInteger enforce) const { if (native()) return xrp().toNumber(enforce); if (mAsset.holds()) return mpt().toNumber(enforce); // It doesn't make sense to enforce limits on IOUs return iou(); } inline STAmount& STAmount::operator=(beast::Zero) { clear(); return *this; } inline STAmount& STAmount::operator=(XRPAmount const& amount) { *this = STAmount(amount); return *this; } inline STAmount& STAmount::operator=(Number const& number) { mIsNegative = number.mantissa() < 0; mValue = mIsNegative ? -number.mantissa() : number.mantissa(); mOffset = number.exponent(); canonicalize(); // Convert it back to a Number to check that it's valid [[maybe_unused]] Number n = *this; return *this; } inline void STAmount::negate() { if (*this != beast::zero) mIsNegative = !mIsNegative; } inline void STAmount::clear() { // The -100 is used to allow 0 to sort less than a small positive values // which have a negative exponent. mOffset = integral() ? 0 : -100; mValue = 0; mIsNegative = false; } inline void STAmount::clear(Asset const& asset) { setIssue(asset); clear(); } inline void STAmount::setIssuer(AccountID const& uIssuer) { mAsset.get().account = uIssuer; } inline STAmount const& STAmount::value() const noexcept { return *this; } inline bool isLegalNet(STAmount const& value) { return !value.native() || (value.mantissa() <= STAmount::cMaxNativeN); } //------------------------------------------------------------------------------ // // Operators // //------------------------------------------------------------------------------ bool operator==(STAmount const& lhs, STAmount const& rhs); bool operator<(STAmount const& lhs, STAmount const& rhs); inline bool operator!=(STAmount const& lhs, STAmount const& rhs) { return !(lhs == rhs); } inline bool operator>(STAmount const& lhs, STAmount const& rhs) { return rhs < lhs; } inline bool operator<=(STAmount const& lhs, STAmount const& rhs) { return !(rhs < lhs); } inline bool operator>=(STAmount const& lhs, STAmount const& rhs) { return !(lhs < rhs); } STAmount operator-(STAmount const& value); //------------------------------------------------------------------------------ // // Arithmetic // //------------------------------------------------------------------------------ STAmount operator+(STAmount const& v1, STAmount const& v2); STAmount operator-(STAmount const& v1, STAmount const& v2); STAmount divide(STAmount const& v1, STAmount const& v2, Asset const& asset); STAmount multiply(STAmount const& v1, STAmount const& v2, Asset const& asset); // multiply rounding result in specified direction STAmount mulRound( STAmount const& v1, STAmount const& v2, Asset const& asset, bool roundUp); // multiply following the rounding directions more precisely. STAmount mulRoundStrict( STAmount const& v1, STAmount const& v2, Asset const& asset, bool roundUp); // divide rounding result in specified direction STAmount divRound( STAmount const& v1, STAmount const& v2, Asset const& asset, bool roundUp); // divide following the rounding directions more precisely. STAmount divRoundStrict( STAmount const& v1, STAmount const& v2, Asset const& asset, bool roundUp); // Someone is offering X for Y, what is the rate? // Rate: smaller is better, the taker wants the most out: in/out // VFALCO TODO Return a Quality object std::uint64_t getRate(STAmount const& offerOut, STAmount const& offerIn); //------------------------------------------------------------------------------ inline bool isXRP(STAmount const& amount) { return amount.native(); } bool canAdd(STAmount const& amt1, STAmount const& amt2); bool canSubtract(STAmount const& amt1, STAmount const& amt2); } // namespace ripple //------------------------------------------------------------------------------ namespace Json { template <> inline ripple::STAmount getOrThrow(Json::Value const& v, ripple::SField const& field) { using namespace ripple; Json::StaticString const& key = field.getJsonName(); if (!v.isMember(key)) Throw(key); Json::Value const& inner = v[key]; return amountFromJson(field, inner); } } // namespace Json #endif