From 2cee8e44e3d9bf4ed60cad7ba87277a147cf0c30 Mon Sep 17 00:00:00 2001 From: Gregory Tsipenyuk Date: Tue, 2 Jul 2024 13:56:35 -0400 Subject: [PATCH] Implement STEitherAmount, STMPTAmount Refactor MPTAmount --- include/xrpl/basics/MPTAmount.h | 175 ++++--------- include/xrpl/basics/Number.h | 2 +- include/xrpl/protocol/MPTIssue.h | 12 + include/xrpl/protocol/STAmount.h | 182 +------------ include/xrpl/protocol/STEitherAmount.h | 110 ++++++++ include/xrpl/protocol/STMPTAmount.h | 96 +++++++ include/xrpl/protocol/STObject.h | 4 +- src/libxrpl/basics/MPTAmount.cpp | 102 ++++++++ src/libxrpl/protocol/MPTIssue.cpp | 17 ++ src/libxrpl/protocol/STAmount.cpp | 132 ---------- src/libxrpl/protocol/STEitherAmount.cpp | 324 ++++++++++++++++++++++++ src/libxrpl/protocol/STMPTAmount.cpp | 178 +++++++++++++ src/libxrpl/protocol/STObject.cpp | 7 - src/test/jtx/impl/mpt.cpp | 2 +- 14 files changed, 895 insertions(+), 448 deletions(-) create mode 100644 include/xrpl/protocol/STEitherAmount.h create mode 100644 include/xrpl/protocol/STMPTAmount.h create mode 100644 src/libxrpl/basics/MPTAmount.cpp create mode 100644 src/libxrpl/protocol/STEitherAmount.cpp create mode 100644 src/libxrpl/protocol/STMPTAmount.cpp diff --git a/include/xrpl/basics/MPTAmount.h b/include/xrpl/basics/MPTAmount.h index 6d1579d11d6..bdb83b17cf9 100644 --- a/include/xrpl/basics/MPTAmount.h +++ b/include/xrpl/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 value_; public: MPTAmount() = default; @@ -52,165 +52,94 @@ class MPTAmount : private boost::totally_ordered, constexpr MPTAmount& operator=(MPTAmount const& other) = default; - constexpr MPTAmount(beast::Zero) : mpt_(0) - { - } + constexpr MPTAmount(beast::Zero); - constexpr explicit MPTAmount(mpt_type value) : mpt_(value) - { - } + constexpr explicit MPTAmount(value_type value); - constexpr MPTAmount& operator=(beast::Zero) - { - mpt_ = 0; - return *this; - } + constexpr MPTAmount& operator=(beast::Zero); MPTAmount& - operator=(mpt_type value) - { - mpt_ = value; - return *this; - } - - constexpr MPTAmount - operator*(mpt_type const& rhs) const - { - return MPTAmount{mpt_ * rhs}; - } - - friend constexpr MPTAmount - operator*(mpt_type lhs, MPTAmount const& rhs) - { - // multiplication is commutative - return rhs * lhs; - } + operator+=(MPTAmount const& other); MPTAmount& - operator+=(MPTAmount const& other) - { - mpt_ += other.mpt(); - return *this; - } - - MPTAmount& - operator-=(MPTAmount const& other) - { - mpt_ -= other.mpt(); - return *this; - } - - MPTAmount& - operator+=(mpt_type const& rhs) - { - mpt_ += rhs; - return *this; - } - - MPTAmount& - operator-=(mpt_type const& rhs) - { - mpt_ -= rhs; - return *this; - } - - MPTAmount& - operator*=(mpt_type const& rhs) - { - mpt_ *= rhs; - return *this; - } + operator-=(MPTAmount const& other); MPTAmount - operator-() const - { - return MPTAmount{-mpt_}; - } + operator-() const; bool - operator==(MPTAmount const& other) const - { - return mpt_ == other.mpt_; - } + operator==(MPTAmount const& other) const; bool - operator==(mpt_type other) const - { - return mpt_ == other; - } + operator==(value_type other) const; bool - operator<(MPTAmount const& other) const - { - return mpt_ < other.mpt_; - } + operator<(MPTAmount const& other) const; /** Returns true if the amount is not zero */ - explicit constexpr operator bool() const noexcept - { - return mpt_ != 0; - } + explicit constexpr operator bool() const noexcept; /** Return the sign of the amount */ constexpr int - signum() const noexcept - { - return (mpt_ < 0) ? -1 : (mpt_ ? 1 : 0); - } + signum() const noexcept; Json::Value - jsonClipped() const - { - static_assert( - std::is_signed_v && std::is_integral_v, - "Expected MPTAmount to be a signed integral type"); - - constexpr auto min = std::numeric_limits::min(); - constexpr auto max = std::numeric_limits::max(); - - if (mpt_ < min) - return min; - if (mpt_ > max) - return max; - return static_cast(mpt_); - } + jsonClipped() const; /** Returns the underlying value. Code SHOULD NOT call this function unless the type has been abstracted away, e.g. in a templated function. */ - constexpr mpt_type - mpt() const - { - return mpt_; - } + constexpr value_type + value() const; friend std::istream& - operator>>(std::istream& s, MPTAmount& val) - { - s >> val.mpt_; - return s; - } + operator>>(std::istream& s, MPTAmount& val); static MPTAmount - minPositiveAmount() - { - return MPTAmount{1}; - } + minPositiveAmount(); }; +constexpr MPTAmount::MPTAmount(value_type value) : value_(value) +{ +} + +constexpr MPTAmount& +MPTAmount::operator=(beast::Zero) +{ + value_ = 0; + return *this; +} + +/** Returns the underlying value. Code SHOULD NOT call this + function unless the type has been abstracted away, + e.g. in a templated function. +*/ +constexpr MPTAmount::value_type +MPTAmount::value() const +{ + return value_; +} + +inline std::istream& +operator>>(std::istream& s, MPTAmount& val) +{ + s >> val.value_; + return s; +} + // Output MPTAmount as just the value. template std::basic_ostream& operator<<(std::basic_ostream& os, const MPTAmount& q) { - return os << q.mpt(); + return os << q.value(); } inline std::string to_string(MPTAmount const& amount) { - return std::to_string(amount.mpt()); + return std::to_string(amount.value()); } inline MPTAmount @@ -225,8 +154,8 @@ mulRatio( if (!den) Throw("division by zero"); - int128_t const amt128(amt.mpt()); - auto const neg = amt.mpt() < 0; + int128_t const amt128(amt.value()); + auto const neg = amt.value() < 0; auto const m = amt128 * num; auto r = m / den; if (m % den) @@ -236,9 +165,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/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index b127d259910..89c15f7d1b8 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -213,7 +213,7 @@ inline Number::Number(XRPAmount const& x) : Number{x.drops()} { } -inline Number::Number(MPTAmount const& x) : Number{x.mpt()} +inline Number::Number(MPTAmount const& x) : Number{x.value()} { } diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h index 3d28d8bb7e3..3ec4a8523c1 100644 --- a/include/xrpl/protocol/MPTIssue.h +++ b/include/xrpl/protocol/MPTIssue.h @@ -35,6 +35,8 @@ class MPTIssue MPTIssue(MPT const& mpt); + MPTIssue(uint192 const& id); + AccountID const& getIssuer() const; @@ -66,6 +68,16 @@ operator!=(MPTIssue const& lhs, MPTIssue const& rhs) return !(lhs.mpt_ == rhs.mpt_); } +MPT +getMPT(uint192 const&); + +inline MPT +badMPT() +{ + static MPT mpt{0, AccountID{0}}; + return mpt; +} + } // namespace ripple #endif // RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index f10bbea446f..a87b14642ce 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -289,161 +289,6 @@ class STAmount final : public STBase, public CountedObject operator+(STAmount const& v1, STAmount const& v2); }; -class STMPTAmount : public STBase, public CountedObject -{ -private: -public: - STMPTAmount() = default; - STMPTAmount(SerialIter& sit, SField const& name) - { - } - SerializedTypeID - getSType() const override - { - return STI_AMOUNT; - } - - std::string - getFullText() const override - { - return ""; - } - - std::string - getText() const override - { - return ""; - } - - Json::Value getJson(JsonOptions) const override - { - return Json::Value{}; - } - - void - add(Serializer& s) const override - { - return; - } - - bool - isEquivalent(const STBase& t) const override - { - return true; - } - - bool - isDefault() const override - { - return true; - } - - AccountID const& - getIssuer() const - { - static AccountID a; - return a; - } -}; - -class STEitherAmount : public STBase, public CountedObject -{ -private: - std::variant amount_; - -public: - using value_type = STAmount; - STEitherAmount() = default; - STEitherAmount(SerialIter& sit, SField const& name) - { - } - STEitherAmount(XRPAmount const& amount) : amount_{amount} - { - } - STEitherAmount(STAmount const& amount) : amount_{amount} - { - } - STEitherAmount(STMPTAmount const& amount) : amount_{amount} - { - } - SerializedTypeID - getSType() const override - { - return STI_AMOUNT; - } - - std::string - getFullText() const override - { - return ""; - } - - std::string - getText() const override - { - return ""; - } - - Json::Value getJson(JsonOptions) const override - { - return Json::Value{}; - } - - void - add(Serializer& s) const override - { - return; - } - - bool - isEquivalent(const STBase& t) const override - { - return true; - } - - bool - isDefault() const override - { - return true; - } - //------------------------------------------------------------------------------ - bool - isMPT() const - { - return std::holds_alternative(amount_); - } - bool - isIssue() const - { - return std::holds_alternative(amount_); - } - operator STAmount() - { - return std::get(amount_); - } - explicit operator STMPTAmount() - { - return std::get(amount_); - } - STAmount const& - value() const - { - return std::get(amount_); - } - STMPTAmount const& - mpt() const - { - return std::get(amount_); - } - AccountID const& - getIssuer() const - { - if (isIssue()) - return value().getIssuer(); - return mpt().getIssuer(); - } -}; - //------------------------------------------------------------------------------ // // Creation @@ -453,20 +298,9 @@ class STEitherAmount : public STBase, public CountedObject // VFALCO TODO The parameter type should be Quality not uint64_t STAmount amountFromQuality(std::uint64_t rate); -class MPTIssue; + STAmount amountFromString(Issue const& issue, std::string const& amount); -STMPTAmount -amountFromString(MPTIssue const& issue, std::string const& amount); - -STEitherAmount -amountFromJson(SField const& name, Json::Value const& v); - -bool -amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource); - -bool -amountFromJsonNoThrow(STEitherAmount& result, Json::Value const& jvSource); // IOUAmount and XRPAmount define toSTAmount, defining this // trivial conversion here makes writing generic code easier @@ -745,18 +579,4 @@ class STAmountSO } // 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 static_cast(amountFromJson(field, inner)); -} -} // namespace Json #endif diff --git a/include/xrpl/protocol/STEitherAmount.h b/include/xrpl/protocol/STEitherAmount.h new file mode 100644 index 00000000000..b5055812052 --- /dev/null +++ b/include/xrpl/protocol/STEitherAmount.h @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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_STEITHERAMOUNT_H_INCLUDED +#define RIPPLE_PROTOCOL_STEITHERAMOUNT_H_INCLUDED + +#include +#include + +namespace ripple { + +class STEitherAmount : public STBase, public CountedObject +{ +private: + std::variant amount_; + +public: + using value_type = STAmount; + STEitherAmount() = default; + STEitherAmount(SerialIter& sit, SField const& name); + STEitherAmount(XRPAmount const& amount); + STEitherAmount(STAmount const& amount); + STEitherAmount(STMPTAmount const& amount); + + SerializedTypeID + getSType() const override; + + std::string + getFullText() const override; + + std::string + getText() const override; + + Json::Value getJson(JsonOptions) const override; + + void + add(Serializer& s) const override; + + bool + isEquivalent(const STBase& t) const override; + + bool + isDefault() const override; + + //------------------------------------------------------------------------------ + + bool + isMPT() const; + + bool + isIssue() const; + + operator STAmount(); + + explicit operator STMPTAmount(); + + STAmount const& + value() const; + + STMPTAmount const& + mpt() const; + + AccountID const& + getIssuer() const; +}; + +STEitherAmount +amountFromJson(SField const& name, Json::Value const& v); + +bool +amountFromJsonNoThrow(STEitherAmount& result, Json::Value const& jvSource); + +bool +amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource); + +} // 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 static_cast(amountFromJson(field, inner)); +} + +} // namespace Json + +#endif // RIPPLE_PROTOCOL_STEITHERAMOUNT_H_INCLUDED diff --git a/include/xrpl/protocol/STMPTAmount.h b/include/xrpl/protocol/STMPTAmount.h new file mode 100644 index 00000000000..8777046781c --- /dev/null +++ b/include/xrpl/protocol/STMPTAmount.h @@ -0,0 +1,96 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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_STMPTAMOUNT_H_INCLUDED +#define RIPPLE_PROTOCOL_STMPTAMOUNT_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace ripple { + +class STMPTAmount : public MPTAmount, + public STBase, + public CountedObject +{ +private: + MPTIssue issue_; + +public: + static constexpr std::uint64_t cMPToken = 0x2000000000000000ull; + + STMPTAmount() = default; + STMPTAmount(SerialIter& sit, SField const& name); + STMPTAmount(SField const& name, MPTIssue const& issue, std::int64_t value); + STMPTAmount(MPTIssue const& issue, std::int64_t value); + + SerializedTypeID + getSType() const override; + + std::string + getFullText() const override; + + std::string + getText() const override; + + Json::Value getJson(JsonOptions) const override; + + void + add(Serializer& s) const override; + + void + setJson(Json::Value& elem) const; + + bool + isEquivalent(const STBase& t) const override; + + bool + isDefault() const override; + + AccountID const& + getIssuer() const; + + friend bool + operator==(STMPTAmount const& lhs, STMPTAmount const& rhs); + + friend bool + operator!=(STMPTAmount const& lhs, STMPTAmount const& rhs); +}; + +inline bool +operator==(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return lhs.value_ == rhs.value_ && lhs.issue_ == rhs.issue_; +} + +inline bool +operator!=(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return !(lhs == rhs); +} + +STMPTAmount +amountFromString(MPTIssue const& issue, std::string const& amount); + +} // namespace ripple + +#endif // RIPPLE_PROTOCOL_STMPTAMOUNT_H_INCLUDED diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index 4f7f3dc362c..a97366f7b4f 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -27,9 +27,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -237,8 +237,6 @@ class STObject : public STBase, public CountedObject getFieldVL(SField const& field) const; STAmount const& getFieldAmount(SField const& field) const; - STMPTAmount const& - getFieldMPTAmount(SField const& field) const; STEitherAmount const& getFieldEitherAmount(SField const& field) const; STPathSet const& diff --git a/src/libxrpl/basics/MPTAmount.cpp b/src/libxrpl/basics/MPTAmount.cpp new file mode 100644 index 00000000000..f761aa93722 --- /dev/null +++ b/src/libxrpl/basics/MPTAmount.cpp @@ -0,0 +1,102 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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 { + +constexpr MPTAmount::MPTAmount(beast::Zero) : value_(0) +{ +} + +MPTAmount& +MPTAmount::operator+=(MPTAmount const& other) +{ + value_ += other.value(); + return *this; +} + +MPTAmount& +MPTAmount::operator-=(MPTAmount const& other) +{ + value_ -= other.value(); + return *this; +} + +MPTAmount +MPTAmount::operator-() const +{ + return MPTAmount{-value_}; +} + +bool +MPTAmount::operator==(MPTAmount const& other) const +{ + return value_ == other.value_; +} + +bool +MPTAmount::operator==(value_type other) const +{ + return value_ == other; +} + +bool +MPTAmount::operator<(MPTAmount const& other) const +{ + return value_ < other.value_; +} + +/** Returns true if the amount is not zero */ +constexpr MPTAmount::operator bool() const noexcept +{ + return value_ != 0; +} + +/** Return the sign of the amount */ +constexpr int +MPTAmount::signum() const noexcept +{ + return (value_ < 0) ? -1 : (value_ ? 1 : 0); +} + +Json::Value +MPTAmount::jsonClipped() const +{ + static_assert( + std::is_signed_v && std::is_integral_v, + "Expected MPTAmount to be a signed integral type"); + + constexpr auto min = std::numeric_limits::min(); + constexpr auto max = std::numeric_limits::max(); + + if (value_ < min) + return min; + if (value_ > max) + return max; + return static_cast(value_); +} + +MPTAmount +MPTAmount::minPositiveAmount() +{ + return MPTAmount{1}; +} + +} // namespace ripple diff --git a/src/libxrpl/protocol/MPTIssue.cpp b/src/libxrpl/protocol/MPTIssue.cpp index fd3b6bbfa90..28806dce3aa 100644 --- a/src/libxrpl/protocol/MPTIssue.cpp +++ b/src/libxrpl/protocol/MPTIssue.cpp @@ -26,6 +26,11 @@ MPTIssue::MPTIssue(MPT const& mpt) : mpt_(mpt) { } +MPTIssue::MPTIssue(uint192 const& id) +{ + mpt_ = getMPT(id); +} + AccountID const& MPTIssue::getIssuer() const { @@ -50,4 +55,16 @@ MPTIssue::getMptID() const return ripple::getMptID(mpt_.second, mpt_.first); } +MPT +getMPT(uint192 const& id) +{ + std::uint32_t sequence; + AccountID account; + + memcpy(&sequence, id.data(), sizeof(sequence)); + sequence = boost::endian::big_to_native(sequence); + memcpy(account.data(), id.data() + sizeof(sequence), sizeof(AccountID)); + return std::make_pair(sequence, account); +} + } // namespace ripple diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index d3b6e9609ef..5ec014287de 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -858,12 +858,6 @@ amountFromQuality(std::uint64_t rate) return STAmount(noIssue(), mantissa, exponent); } -STMPTAmount -amountFromString(MPTIssue const& issue, std::string const& amount) -{ - return STMPTAmount{}; -} - STAmount amountFromString(Issue const& issue, std::string const& amount) { @@ -929,132 +923,6 @@ amountFromString(Issue const& issue, std::string const& amount) return STAmount{issue, mantissa, exponent, negative}; } -STEitherAmount -amountFromJson(SField const& name, Json::Value const& v) -{ - STAmount::mantissa_type mantissa = 0; - STAmount::exponent_type exponent = 0; - bool negative = false; - Issue issue; - - Json::Value value; - Json::Value currency; - Json::Value issuer; - - if (v.isNull()) - { - Throw( - "XRP may not be specified with a null Json value"); - } - else if (v.isObject()) - { - value = v[jss::value]; - currency = v[jss::currency]; - issuer = v[jss::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("invalid amount string"); - - value = elements[0]; - - if (elements.size() > 1) - currency = elements[1]; - - if (elements.size() > 2) - issuer = elements[2]; - } - else - { - value = v; - } - - bool const native = !currency.isString() || currency.asString().empty() || - (currency.asString() == systemCurrencyCode()); - - if (native) - { - if (v.isObjectOrNull()) - Throw("XRP may not be specified as an object"); - issue = xrpIssue(); - } - else - { - // non-XRP - if (!to_currency(issue.currency, currency.asString())) - Throw("invalid currency"); - - if (!issuer.isString() || !to_issuer(issue.account, issuer.asString())) - Throw("invalid issuer"); - - if (isXRP(issue.currency)) - Throw("invalid issuer"); - } - - if (value.isInt()) - { - if (value.asInt() >= 0) - { - mantissa = value.asInt(); - } - else - { - mantissa = -value.asInt(); - negative = true; - } - } - else if (value.isUInt()) - { - mantissa = v.asUInt(); - } - else if (value.isString()) - { - auto const ret = amountFromString(issue, value.asString()); - - mantissa = ret.value().mantissa(); - exponent = ret.value().exponent(); - negative = ret.value().negative(); - } - else - { - Throw("invalid amount type"); - } - - return STAmount{name, issue, mantissa, exponent, native, negative}; -} - -bool -amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource) -{ - try - { - result = static_cast(amountFromJson(sfGeneric, jvSource)); - return true; - } - catch (const std::exception& e) - { - JLOG(debugLog().warn()) - << "amountFromJsonNoThrow: caught: " << e.what(); - } - return false; -} -bool -amountFromJsonNoThrow(STEitherAmount& result, Json::Value const& jvSource) -{ - return true; -} - //------------------------------------------------------------------------------ // // Operators diff --git a/src/libxrpl/protocol/STEitherAmount.cpp b/src/libxrpl/protocol/STEitherAmount.cpp new file mode 100644 index 00000000000..c1ac725eeac --- /dev/null +++ b/src/libxrpl/protocol/STEitherAmount.cpp @@ -0,0 +1,324 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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 +#include +#include + +#include + +namespace ripple { + +STEitherAmount::STEitherAmount(SerialIter& sit, SField const& name) +{ + auto const value = sit.get64(); + sit.reset(); + if ((value & STAmount::cNotNative) == 0 && value & STMPTAmount::cMPToken) + amount_.emplace(sit, name); + else + amount_.emplace(sit, name); +} + +STEitherAmount::STEitherAmount(XRPAmount const& amount) : amount_{amount} +{ +} + +STEitherAmount::STEitherAmount(STAmount const& amount) : amount_{amount} +{ +} + +STEitherAmount::STEitherAmount(STMPTAmount const& amount) : amount_{amount} +{ +} + +SerializedTypeID +STEitherAmount::getSType() const +{ + return STI_AMOUNT; +} + +std::string +STEitherAmount::getFullText() const +{ + return std::visit([&](auto&& a) { return a.getFullText(); }, amount_); +} + +std::string +STEitherAmount::getText() const +{ + return std::visit([&](auto&& a) { return a.getText(); }, amount_); +} + +Json::Value +STEitherAmount::getJson(JsonOptions) const +{ + return std::visit( + [&](auto&& a) { return a.getJson(JsonOptions::none); }, amount_); +} + +void +STEitherAmount::add(Serializer& s) const +{ + std::visit([&](auto&& a) { a.add(s); }, amount_); +} + +bool +STEitherAmount::isEquivalent(const STBase& t) const +{ + return std::visit([&](auto&& a) { return a.isEquivalent(t); }, amount_); +} + +bool +STEitherAmount::isDefault() const +{ + return std::visit([&](auto&& a) { return a.isDefault(); }, amount_); +} +//------------------------------------------------------------------------------ +bool +STEitherAmount::isMPT() const +{ + return std::holds_alternative(amount_); +} + +bool +STEitherAmount::isIssue() const +{ + return std::holds_alternative(amount_); +} + +STEitherAmount::operator STAmount() +{ + if (!isIssue()) + Throw("STEitherAmount is not STAmount"); + return std::get(amount_); +} + +STEitherAmount::operator STMPTAmount() +{ + if (!isMPT()) + Throw("STEitherAmount is not STMPTAmount"); + return std::get(amount_); +} + +STEitherAmount::value_type const& +STEitherAmount::value() const +{ + if (!std::holds_alternative(amount_)) + Throw("STEitherAmount is not STAmount"); + return std::get(amount_); +} + +STMPTAmount const& +STEitherAmount::mpt() const +{ + if (!isMPT()) + Throw("STEitherAmount is not STMPTAmount"); + return std::get(amount_); +} + +AccountID const& +STEitherAmount::getIssuer() const +{ + if (isIssue()) + return value().getIssuer(); + return mpt().getIssuer(); +} + +static bool +validJSONIssue(Json::Value const& jv) +{ + return (jv.isMember(jss::currency) && !jv.isMember(jss::mpt_issuance_id)) || + (!jv.isMember(jss::currency) && !jv.isMember(jss::issuer) && + jv.isMember(jss::mpt_issuance_id)); +} + +STEitherAmount +amountFromJson(SField const& name, Json::Value const& v) +{ + STAmount::mantissa_type mantissa = 0; + STAmount::exponent_type exponent = 0; + bool negative = false; + std::variant issue; + + Json::Value value; + Json::Value currencyOrMPTID; + Json::Value issuer; + bool isMPT = false; + + if (v.isNull()) + { + Throw( + "XRP may not be specified with a null Json value"); + } + else if (v.isObject()) + { + if (!validJSONIssue(v)) + Throw("Invalid Issue's Json specification"); + + value = v[jss::value]; + if (v.isMember(jss::mpt_issuance_id)) + { + isMPT = true; + currencyOrMPTID = v[jss::mpt_issuance_id]; + } + else + { + currencyOrMPTID = v[jss::currency]; + issuer = v[jss::issuer]; + } + } + else if (v.isArray()) + { + value = v.get(Json::UInt(0), 0); + currencyOrMPTID = 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("invalid amount string"); + + value = elements[0]; + + if (elements.size() > 1) + currencyOrMPTID = elements[1]; + + if (elements.size() > 2) + issuer = elements[2]; + } + else + { + value = v; + } + + bool const native = !currencyOrMPTID.isString() || + currencyOrMPTID.asString().empty() || + (currencyOrMPTID.asString() == systemCurrencyCode()); + + if (native) + { + if (v.isObjectOrNull()) + Throw("XRP may not be specified as an object"); + issue = xrpIssue(); + } + else + { + if (isMPT) + { + // sequence (32 bits) + account (160 bits) + uint192 u; + if (!u.parseHex(currencyOrMPTID.asString())) + Throw("invalid MPTokenIssuanceID"); + issue = u; + } + else + { + issue = Issue{}; + if (!to_currency( + std::get(issue).currency, + currencyOrMPTID.asString())) + Throw("invalid currency"); + if (!issuer.isString() || + !to_issuer(std::get(issue).account, issuer.asString())) + Throw("invalid issuer"); + if (isXRP(std::get(issue))) + Throw("invalid issuer"); + } + } + + if (value.isInt()) + { + if (value.asInt() >= 0) + { + mantissa = value.asInt(); + } + else + { + mantissa = -value.asInt(); + negative = true; + } + } + else if (value.isUInt()) + { + mantissa = v.asUInt(); + } + else if (value.isString()) + { + if (std::holds_alternative(issue)) + return amountFromString(std::get(issue), value.asString()); + else + return amountFromString( + std::get(issue), value.asString()); + } + else + { + Throw("invalid amount type"); + } + + if (std::holds_alternative(issue)) + return STAmount{ + name, std::get(issue), mantissa, exponent, native, negative}; + while (exponent-- > 0) + mantissa *= 10; + if (mantissa > 0x8000000000000000) + Throw("MPT amount out of range"); + return STMPTAmount{ + name, std::get(issue), static_cast(mantissa)}; +} + +bool +amountFromJsonNoThrow(STEitherAmount& result, Json::Value const& jvSource) +{ + try + { + result = amountFromJson(sfGeneric, jvSource); + return true; + } + catch (const std::exception& e) + { + JLOG(debugLog().warn()) + << "amountFromJsonNoThrow: caught: " << e.what(); + } + return false; +} + +bool +amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource) +{ + try + { + STEitherAmount amount; + const bool res = amountFromJsonNoThrow(amount, jvSource); + if (res) + result = amount; + return res; + } + catch (const std::exception& e) + { + JLOG(debugLog().warn()) + << "amountFromJsonNoThrow: caught: " << e.what(); + } + return false; +} + +} // namespace ripple diff --git a/src/libxrpl/protocol/STMPTAmount.cpp b/src/libxrpl/protocol/STMPTAmount.cpp new file mode 100644 index 00000000000..b892d21e109 --- /dev/null +++ b/src/libxrpl/protocol/STMPTAmount.cpp @@ -0,0 +1,178 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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 +#include +#include + +#include + +namespace ripple { + +STMPTAmount::STMPTAmount(SerialIter& sit, SField const& name) +{ + value_ = sit.get64(); + assert(value_ & cMPToken); + value_ &= ~cMPToken; + + issue_ = sit.get192(); +} + +STMPTAmount::STMPTAmount( + SField const& name, + MPTIssue const& issue, + value_type value) + : MPTAmount(value), STBase(name), issue_(issue) +{ +} + +STMPTAmount::STMPTAmount(MPTIssue const& issue, value_type value) + : MPTAmount(value), issue_(issue) +{ +} + +SerializedTypeID +STMPTAmount::getSType() const +{ + return STI_AMOUNT; +} + +std::string +STMPTAmount::getFullText() const +{ + std::string ret; + + ret.reserve(64); + ret = getText() + "/" + to_string(issue_.getMptID()); + return ret; +} + +std::string +STMPTAmount::getText() const +{ + return std::to_string(value_); +} + +Json::Value +STMPTAmount::getJson(JsonOptions) const +{ + Json::Value elem; + setJson(elem); + return elem; +} + +void +STMPTAmount::setJson(Json::Value& elem) const +{ + elem[jss::mpt_issuance_id] = to_string(issue_.getMptID()); +} + +void +STMPTAmount::add(Serializer& s) const +{ + s.add64(value_ | cMPToken); + s.addBitString(issue_.getMptID()); +} + +bool +STMPTAmount::isEquivalent(const STBase& t) const +{ + const STMPTAmount* v = dynamic_cast(&t); + return v && (*v == *this); +} + +bool +STMPTAmount::isDefault() const +{ + return value_ == 0 && issue_ == badMPT(); +} + +AccountID const& +STMPTAmount::getIssuer() const +{ + return issue_.getIssuer(); +} + +STMPTAmount +amountFromString(MPTIssue const& issue, std::string const& amount) +{ + static boost::regex const reNumber( + "^" // the beginning of the string + "([+]?)" // (optional) + character (MPT is positive) + "(0|[1-9][0-9]*)" // a number (no leading zeroes, unless 0) + "(\\.([0-9]+))?" // (optional) period followed by any number + "([eE]([+-]?)([0-9]+))?" // (optional) E, optional + or -, any number + "$", + boost::regex_constants::optimize); + + boost::smatch match; + + if (!boost::regex_match(amount, match, reNumber)) + Throw("MPT '" + amount + "' is not valid"); + + // 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 + + // CHECKME: Why 32? Shouldn't this be 16? + if ((match[2].length() + match[4].length()) > 32) + Throw("Number '" + amount + "' is overlong"); + + // Can't specify MPT using fractional representation + if (match[3].matched) + Throw("MPT must be specified as integral."); + + std::int64_t mantissa; + int exponent; + + if (!match[4].matched) // integer only + { + mantissa = + beast::lexicalCastThrow(std::string(match[2])); + exponent = 0; + } + else + { + // integer and fraction + mantissa = beast::lexicalCastThrow(match[2] + match[4]); + exponent = -(match[4].length()); + } + + if (match[5].matched) + { + // we have an exponent + if (match[6].matched && (match[6] == "-")) + exponent -= beast::lexicalCastThrow(std::string(match[7])); + else + exponent += beast::lexicalCastThrow(std::string(match[7])); + } + + while (exponent-- > 0) + mantissa *= 10; + + return {issue, mantissa}; +} + +} // namespace ripple \ No newline at end of file diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 3ed0a2ba2fb..713b8434121 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -644,13 +644,6 @@ STObject::getFieldEitherAmount(SField const& field) const return getFieldByConstRef(field, empty); } -STMPTAmount const& -STObject::getFieldMPTAmount(SField const& field) const -{ - static STMPTAmount const empty{}; - return getFieldByConstRef(field, empty); -} - STPathSet const& STObject::getFieldPathSet(SField const& field) const { diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index d6da2fcfce7..1cef5601e06 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -330,7 +330,7 @@ MPTTester::pay( } else { - STAmount const saAmount = Issue{}; //{*mpt_, amount}; + STAmount const saAmount = Issue{}; //{*value_, amount}; STAmount const saActual = multiply(saAmount, transferRateMPT(*env_.current(), *mpt_)); // Sender pays the transfer fee if any