diff --git a/Builds/levelization/results/loops.txt b/Builds/levelization/results/loops.txt index f703a3a9d5d..fd5d6ffa04b 100644 --- a/Builds/levelization/results/loops.txt +++ b/Builds/levelization/results/loops.txt @@ -5,7 +5,7 @@ Loop: test.jtx test.unit_test test.unit_test == test.jtx Loop: xrpl.basics xrpl.json - xrpl.json ~= xrpl.basics + xrpl.json == xrpl.basics Loop: xrpld.app xrpld.core xrpld.app > xrpld.core diff --git a/include/xrpl/basics/MPTAmount.h b/include/xrpl/basics/MPTAmount.h index 6d1579d11d6..0aaa3a2853f 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,104 @@ class MPTAmount : private boost::totally_ordered, constexpr MPTAmount& operator=(MPTAmount const& other) = default; - constexpr MPTAmount(beast::Zero) : mpt_(0) - { - } - - constexpr explicit MPTAmount(mpt_type value) : mpt_(value) - { - } - - constexpr MPTAmount& operator=(beast::Zero) - { - mpt_ = 0; - return *this; - } - - 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; - } + constexpr explicit MPTAmount(value_type value); - 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; - } + constexpr MPTAmount& operator=(beast::Zero); MPTAmount& - operator-=(mpt_type const& rhs) - { - mpt_ -= rhs; - return *this; - } + operator+=(MPTAmount const& other); 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 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); +} + +/** 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 +164,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 +175,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/basics/base_uint.h b/include/xrpl/basics/base_uint.h index 2bb74791444..0518ee37ea5 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -634,6 +634,7 @@ operator<<(std::ostream& out, base_uint const& u) #ifndef __INTELLISENSE__ static_assert(sizeof(uint128) == 128 / 8, "There should be no padding bytes"); static_assert(sizeof(uint160) == 160 / 8, "There should be no padding bytes"); +static_assert(sizeof(uint192) == 192 / 8, "There should be no padding bytes"); static_assert(sizeof(uint256) == 256 / 8, "There should be no padding bytes"); #endif diff --git a/include/xrpl/protocol/Asset.h b/include/xrpl/protocol/Asset.h deleted file mode 100644 index 9eaadea69d1..00000000000 --- a/include/xrpl/protocol/Asset.h +++ /dev/null @@ -1,138 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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_ASSET_H_INCLUDED -#define RIPPLE_PROTOCOL_ASSET_H_INCLUDED - -#include -#include -#include - -namespace ripple { - -class Asset -{ -private: - std::variant asset_; - -public: - Asset() = default; - - Asset(Issue const& issue); - - Asset(MPTIssue const& mpt); - - Asset(MPT const& mpt); - - Asset(uint192 const& mptID); - - explicit operator Issue() const; - - explicit operator MPTIssue() const; - - AccountID const& - getIssuer() const; - - constexpr Issue const& - issue() const; - - Issue& - issue(); - - constexpr MPTIssue const& - mptIssue() const; - - MPTIssue& - mptIssue(); - - constexpr bool - isMPT() const; - - constexpr bool - isIssue() const; - - std::string - getText() const; - - friend constexpr bool - operator==(Asset const& lhs, Asset const& rhs); - - friend constexpr bool - operator!=(Asset const& lhs, Asset const& rhs); -}; - -constexpr bool -Asset::isMPT() const -{ - return std::holds_alternative(asset_); -} - -constexpr bool -Asset::isIssue() const -{ - return std::holds_alternative(asset_); -} - -constexpr Issue const& -Asset::issue() const -{ - if (!std::holds_alternative(asset_)) - Throw("Asset is not Issue"); - return std::get(asset_); -} - -constexpr MPTIssue const& -Asset::mptIssue() const -{ - if (!std::holds_alternative(asset_)) - Throw("Asset is not MPT"); - return std::get(asset_); -} - -constexpr bool -operator==(Asset const& lhs, Asset const& rhs) -{ - if (lhs.isIssue() != rhs.isIssue()) - Throw("Assets are not comparable"); - if (lhs.isIssue()) - return lhs.issue() == rhs.issue(); - return lhs.mptIssue() == lhs.mptIssue(); -} - -constexpr bool -operator!=(Asset const& lhs, Asset const& rhs) -{ - return !(lhs == rhs); -} - -std::string -to_string(Asset const& asset); - -std::string -to_string(MPTIssue const& mpt); - -std::string -to_string(MPT const& mpt); - -bool -validJSONAsset(Json::Value const& jv); - -} // namespace ripple - -#endif // RIPPLE_PROTOCOL_ASSET_H_INCLUDED diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index a0641dd078a..e9f4100008e 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -291,10 +291,7 @@ Keylet mptIssuance(AccountID const& issuer, std::uint32_t seq) noexcept; Keylet -mptIssuance(uint192 const& mpt) noexcept; - -Keylet -mptIssuance(ripple::MPT const& mpt) noexcept; +mptIssuance(MPTID const& mpt) noexcept; inline Keylet mptIssuance(uint256 const& issuance) @@ -303,10 +300,7 @@ mptIssuance(uint256 const& issuance) } Keylet -mptoken(MPT const& issuanceID, AccountID const& holder) noexcept; - -Keylet -mptoken(uint192 const& issuanceID, AccountID const& holder) noexcept; +mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept; inline Keylet mptoken(uint256 const& mptokenKey) @@ -357,7 +351,7 @@ std::array, 6> const directAccountKeylets{ {&keylet::nftpage_max, jss::NFTokenPage, true}, {&keylet::did, jss::DID, true}}}; -uint192 +MPTID getMptID(AccountID const& account, std::uint32_t sequence); } // namespace ripple diff --git a/include/xrpl/protocol/Issue.h b/include/xrpl/protocol/Issue.h index 106ff62576e..5bf14e81c73 100644 --- a/include/xrpl/protocol/Issue.h +++ b/include/xrpl/protocol/Issue.h @@ -38,9 +38,13 @@ class Issue Currency currency{}; AccountID account{}; - Issue() = default; + Issue() + { + } - Issue(Currency const& c, AccountID const& a); + Issue(Currency const& c, AccountID const& a) : currency(c), account(a) + { + } AccountID const& getIssuer() const; diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h index 3d28d8bb7e3..7bc34adcf53 100644 --- a/include/xrpl/protocol/MPTIssue.h +++ b/include/xrpl/protocol/MPTIssue.h @@ -28,23 +28,17 @@ namespace ripple { class MPTIssue { private: - MPT mpt_; + MPTID mptID_; public: MPTIssue() = default; - MPTIssue(MPT const& mpt); + MPTIssue(MPTID const& id); - AccountID const& + AccountID getIssuer() const; - MPT const& - mpt() const; - - MPT& - mpt(); - - uint192 + MPTID const& getMptID() const; friend constexpr bool @@ -57,15 +51,24 @@ class MPTIssue constexpr bool operator==(MPTIssue const& lhs, MPTIssue const& rhs) { - return lhs.mpt_ == rhs.mpt_; + return lhs.mptID_ == rhs.mptID_; } constexpr bool operator!=(MPTIssue const& lhs, MPTIssue const& rhs) { - return !(lhs.mpt_ == rhs.mpt_); + return !(lhs.mptID_ == rhs.mptID_); +} + +inline bool +isXRP(MPTID const&) +{ + return false; } +Json::Value +to_json(MPTIssue const& issue); + } // namespace ripple #endif // RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED diff --git a/include/xrpl/protocol/Rate.h b/include/xrpl/protocol/Rate.h index b065acb2316..fc9aa7f2c13 100644 --- a/include/xrpl/protocol/Rate.h +++ b/include/xrpl/protocol/Rate.h @@ -21,6 +21,7 @@ #define RIPPLE_PROTOCOL_RATE_H_INCLUDED #include +#include #include #include #include @@ -67,6 +68,9 @@ operator<<(std::ostream& os, Rate const& rate) STAmount multiply(STAmount const& amount, Rate const& rate); +STMPTAmount +multiply(STMPTAmount const& amount, Rate const& rate); + STAmount multiplyRound(STAmount const& amount, Rate const& rate, bool roundUp); diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index b7391d2971b..9090416c514 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -42,6 +42,7 @@ Some fields have a different meaning for their // Forwards class STAccount; +class STEitherAmount; class STAmount; class STIssue; class STBlob; @@ -56,43 +57,49 @@ class STCurrency; #pragma push_macro("XMACRO") #undef XMACRO -#define XMACRO(STYPE) \ - /* special types */ \ - STYPE(STI_UNKNOWN, -2) \ - STYPE(STI_NOTPRESENT, 0) \ - STYPE(STI_UINT16, 1) \ - \ - /* types (common) */ \ - STYPE(STI_UINT32, 2) \ - STYPE(STI_UINT64, 3) \ - STYPE(STI_UINT128, 4) \ - STYPE(STI_UINT256, 5) \ - STYPE(STI_AMOUNT, 6) \ - STYPE(STI_VL, 7) \ - STYPE(STI_ACCOUNT, 8) \ - \ - /* 9-13 are reserved */ \ - STYPE(STI_OBJECT, 14) \ - STYPE(STI_ARRAY, 15) \ - \ - /* types (uncommon) */ \ - STYPE(STI_UINT8, 16) \ - STYPE(STI_UINT160, 17) \ - STYPE(STI_PATHSET, 18) \ - STYPE(STI_VECTOR256, 19) \ - STYPE(STI_UINT96, 20) \ - STYPE(STI_UINT192, 21) \ - STYPE(STI_UINT384, 22) \ - STYPE(STI_UINT512, 23) \ - STYPE(STI_ISSUE, 24) \ - STYPE(STI_XCHAIN_BRIDGE, 25) \ - STYPE(STI_CURRENCY, 26) \ - \ - /* high-level types */ \ - /* cannot be serialized inside other types */ \ - STYPE(STI_TRANSACTION, 10001) \ - STYPE(STI_LEDGERENTRY, 10002) \ - STYPE(STI_VALIDATION, 10003) \ +#define XMACRO(STYPE) \ + /* special types */ \ + STYPE(STI_UNKNOWN, -2) \ + STYPE(STI_NOTPRESENT, 0) \ + STYPE(STI_UINT16, 1) \ + \ + /* types (common) */ \ + STYPE(STI_UINT32, 2) \ + STYPE(STI_UINT64, 3) \ + STYPE(STI_UINT128, 4) \ + STYPE(STI_UINT256, 5) \ + /* Need two enumerators with the same value */ \ + /* so that SF_AMOUNT and SF_EITHER_AMOUNT */ \ + /* map to the same serialization id. */ \ + /* This is an artifact of */ \ + /* CONSTRUCT_TYPED_SFIELD */ \ + STYPE(STI_AMOUNT, 6) \ + STYPE(STI_EITHER_AMOUNT, 6) \ + STYPE(STI_VL, 7) \ + STYPE(STI_ACCOUNT, 8) \ + \ + /* 9-13 are reserved */ \ + STYPE(STI_OBJECT, 14) \ + STYPE(STI_ARRAY, 15) \ + \ + /* types (uncommon) */ \ + STYPE(STI_UINT8, 16) \ + STYPE(STI_UINT160, 17) \ + STYPE(STI_PATHSET, 18) \ + STYPE(STI_VECTOR256, 19) \ + STYPE(STI_UINT96, 20) \ + STYPE(STI_UINT192, 21) \ + STYPE(STI_UINT384, 22) \ + STYPE(STI_UINT512, 23) \ + STYPE(STI_ISSUE, 24) \ + STYPE(STI_XCHAIN_BRIDGE, 25) \ + STYPE(STI_CURRENCY, 26) \ + \ + /* high-level types */ \ + /* cannot be serialized inside other types */ \ + STYPE(STI_TRANSACTION, 10001) \ + STYPE(STI_LEDGERENTRY, 10002) \ + STYPE(STI_VALIDATION, 10003) \ STYPE(STI_METADATA, 10004) #pragma push_macro("TO_ENUM") @@ -322,6 +329,37 @@ struct OptionaledField } }; +/** A field representing a variant with a type known at compile time. + * First template parameter is the variant type, the second + * template parameter is one of its alternative types. A variant field + * enables STObject::operator[]() overload to return the specified + * alternative type. For instance, STEitherAmount is a variant of STAmount + * and STMPTAmount. Some Amount fields, like SFee, don't support MPT + * and are declared as TypedVariantField. + * Conversely, sfAmount field supports MPT and is declared as + * TypedVariantField. Then tx[sfFee] always returns STAmount, + * while tx[sfAmount] returns STEitherAmount and the caller has to get + * the specific type that STEitherAmount holds. + */ +template +struct TypedVariantField : TypedField +{ + template + explicit TypedVariantField( + SField::private_access_tag_t pat, + Args&&... args); +}; + +/** Indicate std::optional variant field semantics. */ +template +struct OptionaledVariantField : OptionaledField +{ + explicit OptionaledVariantField(TypedVariantField const& f_) + : OptionaledField(f_) + { + } +}; + template inline OptionaledField operator~(TypedField const& f) @@ -329,6 +367,13 @@ operator~(TypedField const& f) return OptionaledField(f); } +template +inline OptionaledVariantField +operator~(TypedVariantField const& f) +{ + return OptionaledVariantField(f); +} + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ @@ -346,7 +391,8 @@ using SF_UINT384 = TypedField>; using SF_UINT512 = TypedField>; using SF_ACCOUNT = TypedField; -using SF_AMOUNT = TypedField; +using SF_AMOUNT = TypedVariantField; +using SF_EITHER_AMOUNT = TypedVariantField; using SF_ISSUE = TypedField; using SF_CURRENCY = TypedField; using SF_VL = TypedField; @@ -521,7 +567,7 @@ extern SF_UINT256 const sfHookNamespace; extern SF_UINT256 const sfHookSetTxnID; // currency amount (common) -extern SF_AMOUNT const sfAmount; +extern SF_EITHER_AMOUNT const sfAmount; extern SF_AMOUNT const sfBalance; extern SF_AMOUNT const sfLimitAmount; extern SF_AMOUNT const sfTakerPays; @@ -661,7 +707,6 @@ extern SField const sfXChainClaimProofSig; extern SField const sfXChainCreateAccountProofSig; extern SField const sfXChainClaimAttestationCollectionElement; extern SField const sfXChainCreateAccountAttestationCollectionElement; -extern SField const MPToken; // array of objects (common) // ARRAY/1 is reserved for end of array diff --git a/include/xrpl/protocol/SOTemplate.h b/include/xrpl/protocol/SOTemplate.h index c0fcfb64358..f6c231d1310 100644 --- a/include/xrpl/protocol/SOTemplate.h +++ b/include/xrpl/protocol/SOTemplate.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,9 @@ enum SOEStyle { // constructed with STObject::makeInnerObject() }; +/** Amount fields that can support MPT */ +enum SOETxMPTAmount { soeMPTNone, soeMPTSupported, soeMPTNotSupported }; + //------------------------------------------------------------------------------ /** An element in a SOTemplate. */ @@ -47,10 +51,11 @@ class SOElement // Use std::reference_wrapper so SOElement can be stored in a std::vector. std::reference_wrapper sField_; SOEStyle style_; + SOETxMPTAmount supportMpt_; -public: - SOElement(SField const& fieldName, SOEStyle style) - : sField_(fieldName), style_(style) +private: + void + init(SField const& fieldName) const { if (!sField_.get().isUseful()) { @@ -62,6 +67,28 @@ class SOElement } } +public: + SOElement(SField const& fieldName, SOEStyle style) + : sField_(fieldName), style_(style), supportMpt_(soeMPTNone) + { + init(fieldName); + } + SOElement( + TypedVariantField const& fieldName, + SOEStyle style) + : sField_(fieldName), style_(style), supportMpt_(soeMPTNotSupported) + { + init(fieldName); + } + SOElement( + TypedVariantField const& fieldName, + SOEStyle style, + SOETxMPTAmount supportMpt = soeMPTNotSupported) + : sField_(fieldName), style_(style), supportMpt_(supportMpt) + { + init(fieldName); + } + SField const& sField() const { @@ -73,6 +100,12 @@ class SOElement { return style_; } + + SOETxMPTAmount + supportMPT() const + { + return supportMpt_; + } }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index de66119c80f..6cd5cc7a557 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -23,22 +23,15 @@ #include #include #include -#include #include #include -#include -#include +#include #include #include #include namespace ripple { -template -concept AssetType = std::is_same_v || std::is_same_v || - std::is_same_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: @@ -49,7 +42,7 @@ concept AssetType = std::is_same_v || std::is_same_v || // 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 +class STAmount final { public: using mantissa_type = std::uint64_t; @@ -57,7 +50,7 @@ class STAmount final : public STBase, public CountedObject using rep = std::pair; private: - Asset mAsset; + Issue mIssue; mantissa_type mValue; exponent_type mOffset; bool mIsNative; // A shorthand for isXRP(mIssue). @@ -76,35 +69,21 @@ class STAmount final : public STBase, public CountedObject // Max native value on network. static const std::uint64_t cMaxNativeN = 100000000000000000ull; - static const std::uint64_t cIssuedCurrency = 0x8000000000000000ull; - static const std::uint64_t cPositive = 0x4000000000000000ull; - static const std::uint64_t cMPToken = 0x2000000000000000ull; - static const std::uint64_t cValueMask = ~(cPositive | cMPToken); + static const std::uint64_t cNotNative = 0x8000000000000000ull; + static const std::uint64_t cPosNative = 0x4000000000000000ull; static std::uint64_t const uRateOne; //-------------------------------------------------------------------------- - STAmount(SerialIter& sit, SField const& name); + STAmount(SerialIter& sit); struct unchecked { explicit unchecked() = default; }; - // Do not call canonicalize - template - STAmount( - SField const& name, - A const& asset, - mantissa_type mantissa, - exponent_type exponent, - bool native, - bool negative, - unchecked); - - template STAmount( - A const& asset, + Issue const& issue, mantissa_type mantissa, exponent_type exponent, bool native, @@ -112,68 +91,35 @@ class STAmount final : public STBase, public CountedObject unchecked); // Call canonicalize - template STAmount( - SField const& name, - A const& asset, + Issue const& issue, mantissa_type mantissa, exponent_type exponent, bool native, bool negative); - STAmount(SField const& name, std::int64_t mantissa); - - STAmount( - SField const& name, - std::uint64_t mantissa = 0, - bool negative = false); - - template - STAmount( - SField const& name, - A const& asset, - std::uint64_t mantissa = 0, - int exponent = 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, + Issue const& issue, std::uint64_t mantissa = 0, int exponent = 0, - bool negative = false) - : mAsset(asset) - , mValue(mantissa) - , mOffset(exponent) - , mIsNegative(negative) - { - canonicalize(); - } + bool negative = false); // VFALCO Is this needed when we have the previous signature? - template STAmount( - A const& asset, + Issue const& issue, std::uint32_t mantissa, int exponent = 0, bool negative = false); - template - STAmount(A const& asset, std::int64_t mantissa, int exponent = 0); + STAmount(Issue const& issue, std::int64_t mantissa, int exponent = 0); - template - STAmount(A const& asset, int mantissa, int exponent = 0); + STAmount(Issue const& issue, int mantissa, int exponent = 0); // Legacy support for new-style amounts - template - STAmount(IOUAmount const& amount, A const& asset); + STAmount(IOUAmount const& amount, Issue const& issue); STAmount(XRPAmount const& amount); - template - STAmount(MPTAmount const& amount, A const& asset); operator Number() const; //-------------------------------------------------------------------------- @@ -188,33 +134,15 @@ class STAmount final : public STBase, public CountedObject bool native() const noexcept; - bool - isMPT() const noexcept; - - bool - isIssue() const noexcept; - - bool - isIOU() const noexcept; - - std::string - getTypeName() const noexcept; - bool negative() const noexcept; std::uint64_t mantissa() const noexcept; - Asset const& - asset() const; - Issue const& issue() const; - MPTIssue const& - mptIssue() const; - // These three are deprecated Currency const& getCurrency() const; @@ -272,9 +200,6 @@ class STAmount final : public STBase, public CountedObject void clear(Issue const& issue); - void - clear(MPT const& mpt); - void setIssuer(AccountID const& uIssuer); @@ -289,50 +214,36 @@ class STAmount final : public STBase, public CountedObject //-------------------------------------------------------------------------- SerializedTypeID - getSType() const override; + getSType() const; std::string - getFullText() const override; + getFullText() const; std::string - getText() const override; + getText() const; - Json::Value getJson(JsonOptions) const override; + Json::Value getJson(JsonOptions) const; void - add(Serializer& s) const override; - - bool - isEquivalent(const STBase& t) const override; + add(Serializer& s) const; bool - isDefault() const override; + isDefault() const; XRPAmount xrp() const; IOUAmount iou() const; - MPTAmount - mpt() const; - - template - void - setAsset(A const& a, bool native); private: static std::unique_ptr - construct(SerialIter&, SField const& name); + construct(SerialIter&); 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); @@ -342,140 +253,6 @@ class STAmount final : public STBase, public CountedObject operator+(STAmount const& v1, STAmount const& v2); }; -template -void -STAmount::setAsset(const A& asset, bool native) -{ - if (native) - mAsset = xrpIssue(); - else - mAsset = asset; -} - -template -STAmount::STAmount( - SField const& name, - A const& asset, - mantissa_type mantissa, - exponent_type exponent, - bool native, - bool negative, - unchecked) - : STBase(name) - , mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) -{ - setAsset(asset, native); -} - -template -STAmount::STAmount( - A 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 -STAmount::STAmount( - SField const& name, - A const& asset, - mantissa_type mantissa, - exponent_type exponent, - bool native, - bool negative) - : STBase(name) - , mValue(mantissa) - , mOffset(exponent) - , mIsNative(native) - , mIsNegative(negative) -{ - setAsset(asset, native); - canonicalize(); -} - -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) -{ - assert(mValue <= std::numeric_limits::max()); - 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 -template -STAmount::STAmount(IOUAmount const& amount, A const& asset) - : mAsset(asset) - , mOffset(amount.exponent()) - , mIsNative(false) - , mIsNegative(amount < beast::zero) -{ - if (mIsNegative) - mValue = static_cast(-amount.mantissa()); - else - mValue = static_cast(amount.mantissa()); - - canonicalize(); -} - -template -STAmount::STAmount(MPTAmount const& amount, A const& asset) - : mAsset(asset) - , mOffset(0) - , mIsNative(false) - , mIsNegative(amount < beast::zero) -{ - if (mIsNegative) - mValue = unsafe_cast(-amount.mpt()); - else - mValue = unsafe_cast(amount.mpt()); - - canonicalize(); -} - //------------------------------------------------------------------------------ // // Creation @@ -487,13 +264,7 @@ STAmount amountFromQuality(std::uint64_t rate); STAmount -amountFromString(Asset const& issue, std::string const& amount); - -STAmount -amountFromJson(SField const& name, Json::Value const& v); - -bool -amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource); +amountFromString(Issue const& issue, std::string const& amount); // IOUAmount and XRPAmount define toSTAmount, defining this // trivial conversion here makes writing generic code easier @@ -521,24 +292,6 @@ STAmount::native() const noexcept return mIsNative; } -inline bool -STAmount::isMPT() const noexcept -{ - return mAsset.isMPT(); -} - -inline bool -STAmount::isIssue() const noexcept -{ - return mAsset.isIssue(); -} - -inline bool -STAmount::isIOU() const noexcept -{ - return mAsset.isIssue() && !mIsNative; -} - inline bool STAmount::negative() const noexcept { @@ -551,34 +304,22 @@ STAmount::mantissa() const noexcept return mValue; } -inline Asset const& -STAmount::asset() const -{ - return mAsset; -} - inline Issue const& STAmount::issue() const { - return mAsset.issue(); -} - -inline MPTIssue const& -STAmount::mptIssue() const -{ - return mAsset.mptIssue(); + return mIssue; } inline Currency const& STAmount::getCurrency() const { - return mAsset.issue().currency; + return mIssue.currency; } inline AccountID const& STAmount::getIssuer() const { - return mAsset.getIssuer(); + return mIssue.account; } inline int @@ -590,9 +331,7 @@ STAmount::signum() const noexcept inline STAmount STAmount::zeroed() const { - if (mAsset.isIssue()) - return STAmount(mAsset.issue()); - return STAmount(mAsset.mptIssue()); + return STAmount(mIssue); } inline STAmount::operator bool() const noexcept @@ -604,8 +343,6 @@ inline STAmount::operator Number() const { if (mIsNative) return xrp(); - if (mAsset.isMPT()) - return mpt(); return iou(); } @@ -643,10 +380,7 @@ STAmount::clear() inline void STAmount::clear(STAmount const& saTmpl) { - if (saTmpl.isMPT()) - clear(saTmpl.mAsset.mptIssue()); - else - clear(saTmpl.issue()); + clear(saTmpl.mIssue); } inline void @@ -656,18 +390,11 @@ STAmount::clear(Issue const& issue) clear(); } -inline void -STAmount::clear(MPT const& mpt) -{ - mAsset = mpt; - clear(); -} - inline void STAmount::setIssuer(AccountID const& uIssuer) { - mAsset.issue().account = uIssuer; - setIssue(mAsset.issue()); + mIssue.account = uIssuer; + setIssue(mIssue); } inline STAmount const& @@ -720,6 +447,9 @@ operator>=(STAmount const& lhs, STAmount const& rhs) STAmount operator-(STAmount const& value); +std::ostream& +operator<<(std::ostream& out, const STAmount& t); + //------------------------------------------------------------------------------ // // Arithmetic @@ -732,17 +462,17 @@ STAmount operator-(STAmount const& v1, STAmount const& v2); STAmount -divide(STAmount const& v1, STAmount const& v2, Asset const& asset); +divide(STAmount const& v1, STAmount const& v2, Issue const& issue); STAmount -multiply(STAmount const& v1, STAmount const& v2, Asset const& asset); +multiply(STAmount const& v1, STAmount const& v2, Issue const& issue); // multiply rounding result in specified direction STAmount mulRound( STAmount const& v1, STAmount const& v2, - Asset const& asset, + Issue const& issue, bool roundUp); // multiply following the rounding directions more precisely. @@ -750,7 +480,7 @@ STAmount mulRoundStrict( STAmount const& v1, STAmount const& v2, - Asset const& asset, + Issue const& issue, bool roundUp); // divide rounding result in specified direction @@ -758,7 +488,7 @@ STAmount divRound( STAmount const& v1, STAmount const& v2, - Asset const& asset, + Issue const& issue, bool roundUp); // divide following the rounding directions more precisely. @@ -766,7 +496,7 @@ STAmount divRoundStrict( STAmount const& v1, STAmount const& v2, - Asset const& asset, + Issue const& issue, bool roundUp); // Someone is offering X for Y, what is the rate? @@ -780,13 +510,7 @@ getRate(STAmount const& offerOut, STAmount const& offerIn); inline bool isXRP(STAmount const& amount) { - return amount.isIssue() && isXRP(amount.issue().currency); -} - -inline bool -isMPT(STAmount const& amount) -{ - return amount.isMPT(); + return isXRP(amount.issue().currency); } // Since `canonicalize` does not have access to a ledger, this is needed to put @@ -822,18 +546,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 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..d4b5756451f --- /dev/null +++ b/include/xrpl/protocol/STEitherAmount.h @@ -0,0 +1,264 @@ +//------------------------------------------------------------------------------ +/* + 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 { + +template +concept ValidAmountType = + std::is_same_v || std::is_same_v; + +template +concept EitherAmountType = std::is_same_v || + std::is_same_v>; + +class STEitherAmount : public STBase, public CountedObject +{ +private: + std::variant amount_; + +public: + using value_type = STEitherAmount; + STEitherAmount() = default; + STEitherAmount(SerialIter& sit, SField const& name); + STEitherAmount(XRPAmount const& amount); + STEitherAmount(STAmount const& amount); + STEitherAmount(SField const& name, STAmount const& amount = STAmount{}); + STEitherAmount(SField const& name, STMPTAmount const& amount); + STEitherAmount(STMPTAmount const& amount); + + STEitherAmount& + operator=(STAmount const&); + STEitherAmount& + operator=(STMPTAmount const&); + STEitherAmount& + operator=(XRPAmount const&); + + SerializedTypeID + getSType() const override; + + std::string + getFullText() const override; + + std::string + getText() const override; + + Json::Value getJson(JsonOptions) const override; + + void + setJson(Json::Value&) const; + + void + add(Serializer& s) const override; + + bool + isEquivalent(const STBase& t) const override; + + bool + isDefault() const override; + + //------------------------------------------------------------------------------ + + bool + isMPT() const; + + bool + isIssue() const; + + STEitherAmount const& + value() const; + + std::variant const& + getValue() const; + + std::variant& + getValue(); + + AccountID + getIssuer() const; + + bool + negative() const; + + bool + native() const; + + STEitherAmount + zeroed() const; + + int + signum() const noexcept; + + template + T const& + get() const; + + template + T& + get(); + +private: + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; +}; + +template +T const& +STEitherAmount::get() const +{ + if (std::holds_alternative(amount_)) + return std::get(amount_); + Throw("Invalid STEitherAmount conversion"); +} + +template +T& +STEitherAmount::get() +{ + if (std::holds_alternative(amount_)) + return std::get(amount_); + Throw("Invalid STEitherAmount conversion"); +} + +template +decltype(auto) +get(auto&& amount) +{ + using TAmnt = std::decay_t; + if constexpr (std::is_same_v) + { + if constexpr (std::is_lvalue_reference_v) + return amount.template get(); + else + return T{amount.template get()}; + } + else if constexpr (std::is_same_v>) + { + static std::optional t; + if (amount.has_value()) + return std::make_optional(amount->template get()); + return t; + } + else if constexpr (std::is_convertible_v) + { + if constexpr (std::is_lvalue_reference_v) + return amount.operator STEitherAmount().template get(); + else + return T{amount.operator STEitherAmount().template get()}; + } + else + { + bool const alwaysFalse = !std::is_same_v; + static_assert(alwaysFalse, "Invalid STEitherAmount conversion"); + } +} + +STEitherAmount +amountFromJson(SField const& name, Json::Value const& v); + +STAmount +amountFromJson(SF_AMOUNT const& name, Json::Value const& v); + +bool +amountFromJsonNoThrow(STEitherAmount& result, Json::Value const& jvSource); + +bool +amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource); + +inline bool +operator==(STEitherAmount const& lhs, STEitherAmount const& rhs) +{ + return std::visit( + [&](T1 const& a1, T2 const& a2) { + if constexpr (std::is_same_v) + return a1 == a2; + else + return false; + }, + lhs.getValue(), + rhs.getValue()); +} + +inline bool +operator!=(STEitherAmount const& lhs, STEitherAmount const& rhs) +{ + return !operator==(lhs, rhs); +} + +template +bool +isMPT(T const& amount) +{ + if constexpr (std::is_same_v) + return true; + else if constexpr (std::is_same_v) + return false; +} + +template +bool +isMPT(T const& amount) +{ + if constexpr (std::is_same_v) + return amount.isMPT(); + else + return amount && amount->isMPT(); +} + +template +bool +isIssue(T const& amount) +{ + return !isMPT(amount); +} + +inline bool +isXRP(STEitherAmount const& amount) +{ + if (amount.isIssue()) + return isXRP(get(amount)); + return false; +} + +} // 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 get(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..8e467b6453b --- /dev/null +++ b/include/xrpl/protocol/STMPTAmount.h @@ -0,0 +1,195 @@ +//------------------------------------------------------------------------------ +/* + 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 + +namespace ripple { + +struct Rate; + +class STMPTAmount final : public MPTAmount +{ +private: + MPTIssue issue_; + +public: + static constexpr std::uint8_t cMPToken = 0x20; + static constexpr std::uint8_t cPositive = 0x40; + + STMPTAmount(SerialIter& sit); + STMPTAmount( + MPTIssue const& issue, + std::uint64_t value, + bool negative = false); + STMPTAmount(MPTIssue const& issue, std::int64_t value = 0); + explicit STMPTAmount(value_type value = 0); + + SerializedTypeID + getSType() const; + + std::string + getFullText() const; + + std::string + getText() const; + + Json::Value getJson(JsonOptions) const; + + void + add(Serializer& s) const; + + void + setJson(Json::Value& elem) const; + + bool + isDefault() const; + + AccountID + getIssuer() const; + + MPTIssue const& + issue() const; + + MPTID const& + getCurrency() const; + + void + clear(); + + void + clear(MPTIssue const& issue); + + STMPTAmount + zeroed() const; + + int + signum() const noexcept; + + STMPTAmount& + operator+=(STMPTAmount const& other); + + STMPTAmount& + operator-=(STMPTAmount const& other); + + STMPTAmount + operator-() const; + + STMPTAmount& operator=(beast::Zero); +}; + +inline STMPTAmount +operator+(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + if (lhs.issue() != rhs.issue()) + Throw("Can't add amounts that aren't comparable!"); + return {lhs.issue(), lhs.value() + rhs.value()}; +} + +inline STMPTAmount +operator-(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return lhs + (-rhs); +} + +inline STMPTAmount& +STMPTAmount::operator+=(const ripple::STMPTAmount& other) +{ + *this = *this + other; + return *this; +} + +inline STMPTAmount& +STMPTAmount::operator-=(const ripple::STMPTAmount& other) +{ + *this = *this - other; + return *this; +} + +inline STMPTAmount +STMPTAmount::operator-() const +{ + return {issue_, -value_}; +} + +inline STMPTAmount& STMPTAmount::operator=(beast::Zero) +{ + clear(); + return *this; +} + +inline bool +operator==(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return lhs.issue() == rhs.issue() && lhs.value() == rhs.value(); +} + +inline bool +operator<(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + if (lhs.issue() != rhs.issue()) + Throw( + "Can't compare amounts that are't comparable!"); + return lhs.value() < rhs.value(); +} + +inline bool +operator!=(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return !(lhs == rhs); +} + +inline bool +operator>(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return rhs < lhs; +} + +inline bool +operator<=(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return !(rhs < lhs); +} + +inline bool +operator>=(STMPTAmount const& lhs, STMPTAmount const& rhs) +{ + return !(lhs < rhs); +} + +inline bool +isLegalNet(STMPTAmount const& value) +{ + return true; +} + +STMPTAmount +amountFromString(MPTIssue const& issue, std::string const& amount); + +STMPTAmount +multiply(STMPTAmount const& amount, Rate const& rate); + +} // namespace ripple + +#endif // RIPPLE_PROTOCOL_STMPTAMOUNT_H_INCLUDED diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index e55351cbc24..4d2b061f88a 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -26,10 +26,11 @@ #include #include #include +#include #include -#include #include #include +#include #include #include #include @@ -54,11 +55,11 @@ throwFieldNotFound(SField const& field) class STObject : public STBase, public CountedObject { // Proxy value for a STBase derived class - template + template class Proxy; - template + template class ValueProxy; - template + template class OptionalProxy; struct Transform @@ -235,8 +236,11 @@ class STObject : public STBase, public CountedObject Blob getFieldVL(SField const& field) const; - STAmount const& + STEitherAmount const& getFieldAmount(SField const& field) const; + STAmount const& + getFieldAmount( + TypedVariantField const& field) const; STPathSet const& getFieldPathSet(SField const& field) const; const STVector256& @@ -257,6 +261,12 @@ class STObject : public STBase, public CountedObject typename T::value_type operator[](TypedField const& f) const; + /** Overload for amount field + */ + template + H::value_type + operator[](TypedVariantField const& f) const; + /** Get the value of a field as a std::optional @param An OptionaledField built from an SField value representing the @@ -269,6 +279,12 @@ class STObject : public STBase, public CountedObject std::optional> operator[](OptionaledField const& of) const; + /** Overload for a variant field + */ + template + std::optional> + operator[](OptionaledVariantField const& of) const; + /** Get a modifiable field value. @param A TypedField built from an SField value representing the desired object field. In typical use, the TypedField will be implicitly @@ -280,6 +296,12 @@ class STObject : public STBase, public CountedObject ValueProxy operator[](TypedField const& f); + /** Overload for a variant field + */ + template + ValueProxy + operator[](TypedVariantField const& f); + /** Return a modifiable field value as std::optional @param An OptionaledField built from an SField value representing the @@ -293,6 +315,12 @@ class STObject : public STBase, public CountedObject OptionalProxy operator[](OptionaledField const& of); + /** Overload for a variant field + */ + template + OptionalProxy + operator[](OptionaledVariantField const& of); + /** Get the value of a field. @param A TypedField built from an SField value representing the desired object field. In typical use, the TypedField will be implicitly @@ -304,6 +332,12 @@ class STObject : public STBase, public CountedObject typename T::value_type at(TypedField const& f) const; + /** Overload for a variant field + */ + template + H::value_type + at(TypedVariantField const& f) const; + /** Get the value of a field as std::optional @param An OptionaledField built from an SField value representing the @@ -316,6 +350,12 @@ class STObject : public STBase, public CountedObject std::optional> at(OptionaledField const& of) const; + /** Overload for a variant field + */ + template + std::optional> + at(OptionaledVariantField const& of) const; + /** Get a modifiable field value. @param A TypedField built from an SField value representing the desired object field. In typical use, the TypedField will be implicitly @@ -327,6 +367,12 @@ class STObject : public STBase, public CountedObject ValueProxy at(TypedField const& f); + /** Overload for a variant field + */ + template + ValueProxy + at(TypedVariantField const& f); + /** Return a modifiable field value as std::optional @param An OptionaledField built from an SField value representing the @@ -340,6 +386,12 @@ class STObject : public STBase, public CountedObject OptionalProxy at(OptionaledField const& of); + /** Overload for a variant field + */ + template + OptionalProxy + at(OptionaledVariantField const& of); + /** Set a field. if the field already exists, it is replaced. */ @@ -370,7 +422,7 @@ class STObject : public STBase, public CountedObject setAccountID(SField const& field, AccountID const&); void - setFieldAmount(SField const& field, STAmount const&); + setFieldAmount(SField const& field, STEitherAmount const&); void setFieldIssue(SField const& field, STIssue const&); void @@ -429,6 +481,14 @@ class STObject : public STBase, public CountedObject static std::vector getSortedFields(STObject const& objToSort, WhichFields whichFields); + template + typename T::value_type + atImpl(TypedField const& f) const; + + template + std::optional> + atImpl(OptionaledField const& of) const; + // Implementation for getting (most) fields that return by value. // // The remove_cv and remove_reference are necessitated by the STBitString @@ -475,11 +535,11 @@ class STObject : public STBase, public CountedObject //------------------------------------------------------------------------------ -template +template class STObject::Proxy { protected: - using value_type = typename T::value_type; + using value_type = H::value_type; STObject* st_; SOEStyle style_; @@ -500,11 +560,11 @@ class STObject::Proxy assign(U&& u); }; -template -class STObject::ValueProxy : private Proxy +template +class STObject::ValueProxy : private Proxy { private: - using value_type = typename T::value_type; + using value_type = H::value_type; public: ValueProxy(ValueProxy const&) = default; @@ -523,11 +583,11 @@ class STObject::ValueProxy : private Proxy ValueProxy(STObject* st, TypedField const* f); }; -template -class STObject::OptionalProxy : private Proxy +template +class STObject::OptionalProxy : private Proxy { private: - using value_type = typename T::value_type; + using value_type = H::value_type; using optional_type = std::optional::type>; @@ -659,8 +719,9 @@ class STObject::FieldErr : public std::runtime_error using std::runtime_error::runtime_error; }; -template -STObject::Proxy::Proxy(STObject* st, TypedField const* f) : st_(st), f_(f) +template +STObject::Proxy::Proxy(STObject* st, TypedField const* f) + : st_(st), f_(f) { if (st_->mType) { @@ -676,13 +737,18 @@ STObject::Proxy::Proxy(STObject* st, TypedField const* f) : st_(st), f_(f) } } -template +template auto -STObject::Proxy::value() const -> value_type +STObject::Proxy::value() const -> value_type { auto const t = find(); if (t) - return t->value(); + { + if constexpr (std::is_same_v) + return t->value(); + else + return get(t->value()); + } if (style_ == soeINVALID) { Throw("Value requested from invalid STObject."); @@ -695,17 +761,17 @@ STObject::Proxy::value() const -> value_type return value_type{}; } -template +template inline T const* -STObject::Proxy::find() const +STObject::Proxy::find() const { return dynamic_cast(st_->peekAtPField(*f_)); } -template +template template void -STObject::Proxy::assign(U&& u) +STObject::Proxy::assign(U&& u) { if (style_ == soeDEFAULT && u == value_type{}) { @@ -723,67 +789,68 @@ STObject::Proxy::assign(U&& u) //------------------------------------------------------------------------------ -template +template template -std::enable_if_t, STObject::ValueProxy&> -STObject::ValueProxy::operator=(U&& u) +std::enable_if_t, STObject::ValueProxy&> +STObject::ValueProxy::operator=(U&& u) { this->assign(std::forward(u)); return *this; } -template -STObject::ValueProxy::operator value_type() const +template +STObject::ValueProxy::operator value_type() const { return this->value(); } -template -STObject::ValueProxy::ValueProxy(STObject* st, TypedField const* f) - : Proxy(st, f) +template +STObject::ValueProxy::ValueProxy(STObject* st, TypedField const* f) + : Proxy(st, f) { } //------------------------------------------------------------------------------ -template -STObject::OptionalProxy::operator bool() const noexcept +template +STObject::OptionalProxy::operator bool() const noexcept { return engaged(); } -template +template auto -STObject::OptionalProxy::operator*() const -> value_type +STObject::OptionalProxy::operator*() const -> value_type { return this->value(); } -template -STObject::OptionalProxy::operator typename STObject::OptionalProxy< - T>::optional_type() const +template +STObject::OptionalProxy::operator typename STObject::OptionalProxy:: + optional_type() const { return optional_value(); } -template -typename STObject::OptionalProxy::optional_type -STObject::OptionalProxy::operator~() const +template +typename STObject::OptionalProxy::optional_type +STObject::OptionalProxy::operator~() const { return optional_value(); } -template +template auto -STObject::OptionalProxy::operator=(std::nullopt_t const&) -> OptionalProxy& +STObject::OptionalProxy::operator=(std::nullopt_t const&) + -> OptionalProxy& { disengage(); return *this; } -template +template auto -STObject::OptionalProxy::operator=(optional_type&& v) -> OptionalProxy& +STObject::OptionalProxy::operator=(optional_type&& v) -> OptionalProxy& { if (v) this->assign(std::move(*v)); @@ -792,9 +859,10 @@ STObject::OptionalProxy::operator=(optional_type&& v) -> OptionalProxy& return *this; } -template +template auto -STObject::OptionalProxy::operator=(optional_type const& v) -> OptionalProxy& +STObject::OptionalProxy::operator=(optional_type const& v) + -> OptionalProxy& { if (v) this->assign(*v); @@ -803,31 +871,33 @@ STObject::OptionalProxy::operator=(optional_type const& v) -> OptionalProxy& return *this; } -template +template template -std::enable_if_t, STObject::OptionalProxy&> -STObject::OptionalProxy::operator=(U&& u) +std::enable_if_t, STObject::OptionalProxy&> +STObject::OptionalProxy::operator=(U&& u) { this->assign(std::forward(u)); return *this; } -template -STObject::OptionalProxy::OptionalProxy(STObject* st, TypedField const* f) - : Proxy(st, f) +template +STObject::OptionalProxy::OptionalProxy( + STObject* st, + TypedField const* f) + : Proxy(st, f) { } -template +template bool -STObject::OptionalProxy::engaged() const noexcept +STObject::OptionalProxy::engaged() const noexcept { return this->style_ == soeDEFAULT || this->find() != nullptr; } -template +template void -STObject::OptionalProxy::disengage() +STObject::OptionalProxy::disengage() { if (this->style_ == soeREQUIRED || this->style_ == soeDEFAULT) Throw( @@ -838,18 +908,18 @@ STObject::OptionalProxy::disengage() this->st_->makeFieldAbsent(*this->f_); } -template +template auto -STObject::OptionalProxy::optional_value() const -> optional_type +STObject::OptionalProxy::optional_value() const -> optional_type { if (!engaged()) return std::nullopt; return this->value(); } -template -typename STObject::OptionalProxy::value_type -STObject::OptionalProxy::value_or(value_type val) const +template +typename STObject::OptionalProxy::value_type +STObject::OptionalProxy::value_or(value_type val) const { return engaged() ? this->value() : val; } @@ -961,6 +1031,13 @@ STObject::operator[](TypedField const& f) const return at(f); } +template +inline H::value_type +STObject::operator[](TypedVariantField const& f) const +{ + return at(f); +} + template std::optional> STObject::operator[](OptionaledField const& of) const @@ -968,6 +1045,13 @@ STObject::operator[](OptionaledField const& of) const return at(of); } +template +inline std::optional> +STObject::operator[](OptionaledVariantField const& of) const +{ + return at(of); +} + template inline auto STObject::operator[](TypedField const& f) -> ValueProxy @@ -975,6 +1059,13 @@ STObject::operator[](TypedField const& f) -> ValueProxy return at(f); } +template +inline auto +STObject::operator[](TypedVariantField const& f) -> ValueProxy +{ + return at(f); +} + template inline auto STObject::operator[](OptionaledField const& of) -> OptionalProxy @@ -982,9 +1073,17 @@ STObject::operator[](OptionaledField const& of) -> OptionalProxy return at(of); } +template +inline auto +STObject::operator[](OptionaledVariantField const& of) + -> OptionalProxy +{ + return at(of); +} + template typename T::value_type -STObject::at(TypedField const& f) const +STObject::atImpl(TypedField const& f) const { auto const b = peekAtPField(f); if (!b) @@ -1009,9 +1108,26 @@ STObject::at(TypedField const& f) const return dv; } +template +typename T::value_type +STObject::at(TypedField const& f) const +{ + return atImpl(f); +} + +template +inline H::value_type +STObject::at(TypedVariantField const& f) const +{ + if constexpr (std::is_same_v) + return atImpl(f); + else + return get(atImpl(f)); +} + template std::optional> -STObject::at(OptionaledField const& of) const +STObject::atImpl(OptionaledField const& of) const { auto const b = peekAtPField(*of.f); if (!b) @@ -1029,6 +1145,23 @@ STObject::at(OptionaledField const& of) const return u->value(); } +template +std::optional> +STObject::at(OptionaledField const& of) const +{ + return atImpl(of); +} + +template +inline std::optional> +STObject::at(OptionaledVariantField const& of) const +{ + if constexpr (std::is_same_v) + return atImpl(of); + else + return get(atImpl(of)); +} + template inline auto STObject::at(TypedField const& f) -> ValueProxy @@ -1036,6 +1169,13 @@ STObject::at(TypedField const& f) -> ValueProxy return ValueProxy(this, &f); } +template +inline auto +STObject::at(TypedVariantField const& f) -> ValueProxy +{ + return ValueProxy(this, &f); +} + template inline auto STObject::at(OptionaledField const& of) -> OptionalProxy @@ -1043,6 +1183,13 @@ STObject::at(OptionaledField const& of) -> OptionalProxy return OptionalProxy(this, of.f); } +template +inline auto +STObject::at(OptionaledVariantField const& of) -> OptionalProxy +{ + return OptionalProxy(this, of.f); +} + template void STObject::setFieldH160(SField const& field, base_uint<160, Tag> const& v) diff --git a/include/xrpl/protocol/Serializer.h b/include/xrpl/protocol/Serializer.h index d8d0b9222e3..dac1cc6132b 100644 --- a/include/xrpl/protocol/Serializer.h +++ b/include/xrpl/protocol/Serializer.h @@ -344,6 +344,10 @@ class SerialIter return static_cast(remain_); } + // peek function, throw on error + unsigned char + peek8(); + // get functions throw on error unsigned char get8(); diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 3f366ca9350..605e7f6073a 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -139,8 +139,7 @@ enum TEMcodes : TERUnderlyingType { temEMPTY_DID, temARRAY_EMPTY, - temARRAY_TOO_LARGE, - temMPT_NOT_SUPPORTED + temARRAY_TOO_LARGE }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h index 7932a4c55a3..92114230da7 100644 --- a/include/xrpl/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -108,12 +108,12 @@ class TxMeta } void - setDeliveredAmount(STAmount const& delivered) + setDeliveredAmount(STEitherAmount const& delivered) { mDelivered = delivered; } - STAmount + STEitherAmount getDeliveredAmount() const { assert(hasDeliveredAmount()); @@ -132,7 +132,7 @@ class TxMeta std::uint32_t mIndex; int mResult; - std::optional mDelivered; + std::optional mDelivered; STArray mNodes; }; diff --git a/include/xrpl/protocol/UintTypes.h b/include/xrpl/protocol/UintTypes.h index b574319b734..cf34366262f 100644 --- a/include/xrpl/protocol/UintTypes.h +++ b/include/xrpl/protocol/UintTypes.h @@ -46,12 +46,6 @@ class NodeIDTag explicit NodeIDTag() = default; }; -class MPTTag -{ -public: - explicit MPTTag() = default; -}; - } // namespace detail /** Directory is an index into the directory of offer books. @@ -64,8 +58,12 @@ using Currency = base_uint<160, detail::CurrencyTag>; /** NodeID is a 160-bit hash representing one node. */ using NodeID = base_uint<160, detail::NodeIDTag>; -/** MPT is a 192-bit hash representing MPTID. */ -using MPT = std::pair; +/** MPT is a 192-bit hash representing MPTID. + * Currently MPTID is the only 192-bit field. + * If other 192-bit fields with different semantics + * are added then MPTID must change to a unique tag. + */ +using MPTID = base_uint<192>; /** XRP currency. */ Currency const& @@ -76,7 +74,7 @@ Currency const& noCurrency(); /** A placeholder for empty MPTID. */ -MPT const& +MPTID const& noMPT(); /** We deliberately disallow the currency that looks like "XRP" because too diff --git a/include/xrpl/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h index 721950ca9c1..ea715aa30ab 100644 --- a/include/xrpl/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -143,7 +143,7 @@ struct AttestationClaim : AttestationBase message( STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, + STEitherAmount const& sendingAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t claimID, @@ -226,8 +226,8 @@ struct AttestationCreateAccount : AttestationBase message( STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, - STAmount const& rewardAmount, + STEitherAmount const& sendingAmount, + STEitherAmount const& rewardAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, diff --git a/src/libxrpl/basics/MPTAmount.cpp b/src/libxrpl/basics/MPTAmount.cpp new file mode 100644 index 00000000000..6c9a50e4730 --- /dev/null +++ b/src/libxrpl/basics/MPTAmount.cpp @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +/* + 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 { + +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_; +} + +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/Asset.cpp b/src/libxrpl/protocol/Asset.cpp deleted file mode 100644 index d3bd567a1fd..00000000000 --- a/src/libxrpl/protocol/Asset.cpp +++ /dev/null @@ -1,118 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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 - -namespace ripple { - -Asset::Asset(Issue const& issue) : asset_(issue) -{ -} - -Asset::Asset(MPTIssue const& mpt) : asset_(mpt) -{ -} - -Asset::Asset(MPT const& mpt) : asset_(MPTIssue{mpt}) -{ -} - -Asset::operator Issue() const -{ - return issue(); -} - -Asset::operator MPTIssue() const -{ - return mptIssue(); -} - -AccountID const& -Asset::getIssuer() const -{ - if (isIssue()) - return issue().getIssuer(); - return mptIssue().getIssuer(); -} - -Asset::Asset(uint192 const& u) -{ - std::uint32_t sequence; - AccountID account; - memcpy(&sequence, u.data(), sizeof(sequence)); - sequence = boost::endian::big_to_native(sequence); - memcpy(account.data(), u.data() + sizeof(sequence), sizeof(AccountID)); - asset_ = std::make_pair(sequence, account); -} - -Issue& -Asset::issue() -{ - if (!std::holds_alternative(asset_)) - Throw("Asset is not Issue"); - return std::get(asset_); -} - -MPTIssue& -Asset::mptIssue() -{ - if (!std::holds_alternative(asset_)) - Throw("Asset is not MPT"); - return std::get(asset_); -} - -std::string -Asset::getText() const -{ - if (isIssue()) - return issue().getText(); - return to_string(mptIssue().getMptID()); -} - -std::string -to_string(Asset const& asset) -{ - if (asset.isIssue()) - return to_string(asset.issue()); - return to_string(asset.mptIssue().getMptID()); -} - -std::string -to_string(MPTIssue const& mptIssue) -{ - return to_string(mptIssue.getMptID()); -} - -std::string -to_string(MPT const& mpt) -{ - return to_string(getMptID(mpt.second, mpt.first)); -} - -bool -validJSONAsset(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)); -} - -} // namespace ripple \ No newline at end of file diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index b402111972c..8014d8d4dcd 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -137,10 +137,10 @@ getTicketIndex(AccountID const& account, SeqProxy ticketSeq) return getTicketIndex(account, ticketSeq.value()); } -uint192 +MPTID getMptID(AccountID const& account, std::uint32_t sequence) { - uint192 u; + MPTID u; sequence = boost::endian::native_to_big(sequence); memcpy(u.data(), &sequence, sizeof(sequence)); memcpy(u.data() + sizeof(sequence), account.data(), sizeof(account)); @@ -470,31 +470,18 @@ mptIssuance(AccountID const& issuer, std::uint32_t seq) noexcept } Keylet -mptIssuance(ripple::MPT const& mpt) noexcept -{ - return mptIssuance(mpt.second, mpt.first); -} - -Keylet -mptIssuance(uint192 const& mpt) noexcept +mptIssuance(MPTID const& id) noexcept { return { - ltMPTOKEN_ISSUANCE, indexHash(LedgerNameSpace::MPTOKEN_ISSUANCE, mpt)}; + ltMPTOKEN_ISSUANCE, indexHash(LedgerNameSpace::MPTOKEN_ISSUANCE, id)}; } Keylet -mptoken(uint192 const& issuanceID, AccountID const& holder) noexcept +mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept { return mptoken(mptIssuance(issuanceID).key, holder); } -Keylet -mptoken(MPT const& mptID, AccountID const& holder) noexcept -{ - return mptoken( - mptIssuance(getMptID(mptID.second, mptID.first)).key, holder); -} - Keylet mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept { diff --git a/src/libxrpl/protocol/Issue.cpp b/src/libxrpl/protocol/Issue.cpp index 8aff535fde7..d860e7062bc 100644 --- a/src/libxrpl/protocol/Issue.cpp +++ b/src/libxrpl/protocol/Issue.cpp @@ -26,10 +26,6 @@ namespace ripple { -Issue::Issue(Currency const& c, AccountID const& a) : currency(c), account(a) -{ -} - AccountID const& Issue::getIssuer() const { diff --git a/src/libxrpl/protocol/MPTIssue.cpp b/src/libxrpl/protocol/MPTIssue.cpp index fd3b6bbfa90..629f00be2d2 100644 --- a/src/libxrpl/protocol/MPTIssue.cpp +++ b/src/libxrpl/protocol/MPTIssue.cpp @@ -19,35 +19,39 @@ #include #include +#include namespace ripple { -MPTIssue::MPTIssue(MPT const& mpt) : mpt_(mpt) +MPTIssue::MPTIssue(MPTID const& id) : mptID_(id) { } -AccountID const& +AccountID MPTIssue::getIssuer() const { - return mpt_.second; + AccountID account; + + // copy from id skipping the sequence + memcpy( + account.data(), + mptID_.data() + sizeof(std::uint32_t), + sizeof(AccountID)); + return account; } -MPT const& -MPTIssue::mpt() const -{ - return mpt_; -} - -MPT& -MPTIssue::mpt() +MPTID const& +MPTIssue::getMptID() const { - return mpt_; + return mptID_; } -uint192 -MPTIssue::getMptID() const +Json::Value +to_json(MPTIssue const& issue) { - return ripple::getMptID(mpt_.second, mpt_.first); + Json::Value jv; + jv[jss::mpt_issuance_id] = to_string(issue.getMptID()); + return jv; } } // namespace ripple diff --git a/src/libxrpl/protocol/Quality.cpp b/src/libxrpl/protocol/Quality.cpp index c6464eba9d2..38b641328b0 100644 --- a/src/libxrpl/protocol/Quality.cpp +++ b/src/libxrpl/protocol/Quality.cpp @@ -65,7 +65,7 @@ Quality::operator--(int) } template + *DivRoundFunc)(STAmount const&, STAmount const&, Issue const&, bool)> static Amounts ceil_in_impl( Amounts const& amount, @@ -77,7 +77,7 @@ ceil_in_impl( { Amounts result( limit, - DivRoundFunc(limit, quality.rate(), amount.out.asset(), roundUp)); + DivRoundFunc(limit, quality.rate(), amount.out.issue(), roundUp)); // Clamp out if (result.out > amount.out) result.out = amount.out; @@ -104,7 +104,7 @@ Quality::ceil_in_strict( } template + *MulRoundFunc)(STAmount const&, STAmount const&, Issue const&, bool)> static Amounts ceil_out_impl( Amounts const& amount, @@ -115,7 +115,7 @@ ceil_out_impl( if (amount.out > limit) { Amounts result( - MulRoundFunc(limit, quality.rate(), amount.in.asset(), roundUp), + MulRoundFunc(limit, quality.rate(), amount.in.issue(), roundUp), limit); // Clamp in if (result.in > amount.in) @@ -151,7 +151,7 @@ composed_quality(Quality const& lhs, Quality const& rhs) STAmount const rhs_rate(rhs.rate()); assert(rhs_rate != beast::zero); - STAmount const rate(mulRound(lhs_rate, rhs_rate, lhs_rate.asset(), true)); + STAmount const rate(mulRound(lhs_rate, rhs_rate, lhs_rate.issue(), true)); std::uint64_t const stored_exponent(rate.exponent() + 100); std::uint64_t const stored_mantissa(rate.mantissa()); diff --git a/src/libxrpl/protocol/Rate2.cpp b/src/libxrpl/protocol/Rate2.cpp index 01a3e7deca5..ecf6de2cb77 100644 --- a/src/libxrpl/protocol/Rate2.cpp +++ b/src/libxrpl/protocol/Rate2.cpp @@ -51,7 +51,20 @@ multiply(STAmount const& amount, Rate const& rate) if (rate == parityRate) return amount; - return multiply(amount, detail::as_amount(rate), amount.asset()); + return multiply(amount, detail::as_amount(rate), amount.issue()); +} + +STMPTAmount +multiply(STMPTAmount const& amount, Rate const& rate) +{ + assert(rate.value != 0); + + if (rate == parityRate) + return amount; + + return STMPTAmount{ + amount.issue(), + static_cast(amount.value() * Number{rate.value, -9})}; } STAmount @@ -62,7 +75,7 @@ multiplyRound(STAmount const& amount, Rate const& rate, bool roundUp) if (rate == parityRate) return amount; - return mulRound(amount, detail::as_amount(rate), amount.asset(), roundUp); + return mulRound(amount, detail::as_amount(rate), amount.issue(), roundUp); } STAmount diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index dc4bfb69a21..21431cad5d7 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -45,6 +45,15 @@ TypedField::TypedField(private_access_tag_t pat, Args&&... args) { } +template +template +TypedVariantField::TypedVariantField( + SField::private_access_tag_t pat, + Args&&... args) + : TypedField(pat, std::forward(args)...) +{ +} + // Construct all compile-time SFields, and register them in the knownCodeToField // database: @@ -248,7 +257,7 @@ CONSTRUCT_TYPED_SFIELD(sfHookNamespace, "HookNamespace", UINT256, CONSTRUCT_TYPED_SFIELD(sfHookSetTxnID, "HookSetTxnID", UINT256, 33); // currency amount (common) -CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1); +CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", EITHER_AMOUNT, 1); CONSTRUCT_TYPED_SFIELD(sfBalance, "Balance", AMOUNT, 2); CONSTRUCT_TYPED_SFIELD(sfLimitAmount, "LimitAmount", AMOUNT, 3); CONSTRUCT_TYPED_SFIELD(sfTakerPays, "TakerPays", AMOUNT, 4); diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 355518347a9..99eb4b829c5 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -79,56 +78,25 @@ getSNValue(STAmount const& amount) return ret; } -static std::int64_t -getMPTValue(STAmount const& amount) -{ - if (!amount.isMPT()) - Throw("amount is not native!"); - - auto ret = static_cast(amount.mantissa()); - - assert(static_cast(ret) == amount.mantissa()); - - if (amount.negative()) - ret = -ret; - - return ret; -} - static bool areComparable(STAmount const& v1, STAmount 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); + return v1.native() == v2.native() && + v1.issue().currency == v2.issue().currency; } -STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) +STAmount::STAmount(SerialIter& sit) { - // TODO MPT make sure backward compatible - std::uint64_t value = sit.get64(); - // TODO must fix serialization for IOU, it incorrectly sets cMPToken - bool isMPT = (value & cMPToken) && !(value & cIssuedCurrency); - - // native or MPT - if ((value & cIssuedCurrency) == 0 || isMPT) + // native + if ((value & cNotNative) == 0) { - if (isMPT) - { - // mAsset = std::make_pair( - // sit.get32(), static_cast(sit.get160())); - mAsset = sit.get192(); - } - else - mAsset = xrpIssue(); // positive - if ((value & cPositive) != 0) + if ((value & cPosNative) != 0) { - mValue = value & cValueMask; + mValue = value & ~cPosNative; mOffset = 0; - mIsNative = !isMPT; + mIsNative = true; mIsNegative = false; return; } @@ -137,9 +105,9 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) if (value == 0) Throw("negative zero is not canonical"); - mValue = value & cValueMask; + mValue = value; mOffset = 0; - mIsNative = !isMPT; + mIsNative = true; mIsNegative = true; return; } @@ -171,7 +139,7 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) Throw("invalid currency value"); } - mAsset = issue; + mIssue = issue; mValue = value; mOffset = offset; mIsNegative = isNegative; @@ -182,46 +150,47 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) if (offset != 512) Throw("invalid currency value"); - mAsset = issue; + mIssue = issue; mValue = 0; mOffset = 0; mIsNegative = false; canonicalize(); } -STAmount::STAmount(SField const& name, std::int64_t mantissa) - : STBase(name), mAsset(xrpIssue()), mOffset(0), mIsNative(true) -{ - set(mantissa); -} - -STAmount::STAmount(SField const& name, std::uint64_t mantissa, bool negative) - : STBase(name) - , mAsset(xrpIssue()) +STAmount::STAmount( + Issue const& issue, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative, + unchecked) + : mIssue(issue) , mValue(mantissa) - , mOffset(0) - , mIsNative(true) + , mOffset(exponent) + , mIsNative(native) , mIsNegative(negative) { - assert(mValue <= std::numeric_limits::max()); } -STAmount::STAmount(SField const& name, STAmount const& from) - : STBase(name) - , mAsset(from.mAsset) - , mValue(from.mValue) - , mOffset(from.mOffset) - , mIsNegative(from.mIsNegative) +STAmount::STAmount( + Issue const& issue, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative) + : mIssue(issue) + , mValue(mantissa) + , mOffset(exponent) + , mIsNative(native) + , mIsNegative(negative) { - assert(mValue <= std::numeric_limits::max()); canonicalize(); } //------------------------------------------------------------------------------ STAmount::STAmount(std::uint64_t mantissa, bool negative) - : mAsset(xrpIssue()) - , mValue(mantissa) + : mValue(mantissa) , mOffset(0) , mIsNative(true) , mIsNegative(mantissa != 0 && negative) @@ -229,36 +198,67 @@ STAmount::STAmount(std::uint64_t mantissa, bool negative) assert(mValue <= std::numeric_limits::max()); } -STAmount::STAmount(XRPAmount const& amount) - : mAsset(xrpIssue()) - , mOffset(0) - , mIsNative(true) +STAmount::STAmount( + Issue const& issue, + std::uint64_t mantissa, + int exponent, + bool negative) + : mIssue(issue), mValue(mantissa), mOffset(exponent), mIsNegative(negative) +{ + canonicalize(); +} + +STAmount::STAmount(Issue const& issue, std::int64_t mantissa, int exponent) + : mIssue(issue), mOffset(exponent) +{ + set(mantissa); + canonicalize(); +} + +STAmount::STAmount( + Issue const& issue, + std::uint32_t mantissa, + int exponent, + bool negative) + : STAmount(issue, safe_cast(mantissa), exponent, negative) +{ +} + +STAmount::STAmount(Issue const& issue, int mantissa, int exponent) + : STAmount(issue, safe_cast(mantissa), exponent) +{ +} + +// Legacy support for new-style amounts +STAmount::STAmount(IOUAmount const& amount, Issue const& issue) + : mIssue(issue) + , mOffset(amount.exponent()) + , mIsNative(false) , mIsNegative(amount < beast::zero) { if (mIsNegative) - mValue = unsafe_cast(-amount.drops()); + mValue = static_cast(-amount.mantissa()); else - mValue = unsafe_cast(amount.drops()); + mValue = static_cast(amount.mantissa()); canonicalize(); } -std::unique_ptr -STAmount::construct(SerialIter& sit, SField const& name) +STAmount::STAmount(XRPAmount const& amount) + : mOffset(0), mIsNative(true), mIsNegative(amount < beast::zero) { - return std::make_unique(sit, name); -} + if (mIsNegative) + mValue = unsafe_cast(-amount.drops()); + else + mValue = unsafe_cast(amount.drops()); -STBase* -STAmount::copy(std::size_t n, void* buf) const -{ - return emplace(n, buf, *this); + canonicalize(); } -STBase* -STAmount::move(std::size_t n, void* buf) +std::unique_ptr +STAmount::construct(SerialIter& sit) { - return emplace(n, buf, std::move(*this)); + return std::make_unique(sit); } //------------------------------------------------------------------------------ @@ -284,7 +284,7 @@ STAmount::xrp() const IOUAmount STAmount::iou() const { - if (mIsNative || isMPT()) + if (mIsNative) Throw("Cannot return native STAmount as IOUAmount"); auto mantissa = static_cast(mValue); @@ -296,20 +296,6 @@ STAmount::iou() const return {mantissa, exponent}; } -MPTAmount -STAmount::mpt() const -{ - if (!isMPT()) - Throw("Cannot return STAmount as MPTAmount"); - - auto value = static_cast(mValue); - - if (mIsNegative) - value = -value; - - return MPTAmount{value}; -} - STAmount& STAmount::operator=(IOUAmount const& iou) { @@ -355,19 +341,16 @@ operator+(STAmount const& v1, STAmount const& v2) if (v1 == beast::zero) { // Result must be in terms of v1 currency and issuer. - return { - v1.getFName(), - v1.asset(), - v2.mantissa(), - v2.exponent(), - v2.negative()}; + return {v1.issue(), v2.mantissa(), v2.exponent(), v2.negative()}; } - // TODO if (v1.native()) - return {v1.getFName(), getSNValue(v1) + getSNValue(v2)}; - if (v1.isMPT()) - return {v1.mAsset, v1.mpt().mpt() + v2.mpt().mpt()}; + { + auto const res = getSNValue(v1) + getSNValue(v2); + auto const negative = res < 0; + return STAmount{ + static_cast(negative ? -res : res), negative}; + } if (getSTNumberSwitchover()) { @@ -404,18 +387,12 @@ operator+(STAmount const& v1, STAmount const& v2) std::int64_t fv = vv1 + vv2; if ((fv >= -10) && (fv <= 10)) - return {v1.getFName(), v1.asset()}; + return {v1.issue()}; if (fv >= 0) - return STAmount{ - v1.getFName(), - v1.asset(), - static_cast(fv), - ov1, - false}; - - return STAmount{ - v1.getFName(), v1.asset(), static_cast(-fv), ov1, true}; + return STAmount{v1.issue(), static_cast(fv), ov1, false}; + + return STAmount{v1.issue(), static_cast(-fv), ov1, true}; } STAmount @@ -431,7 +408,7 @@ std::uint64_t const STAmount::uRateOne = getRate(STAmount(1), STAmount(1)); void STAmount::setIssue(Issue const& issue) { - mAsset = issue; + mIssue = issue; mIsNative = isXRP(*this); } @@ -476,13 +453,8 @@ STAmount::setJson(Json::Value& elem) const // It is an error for currency or issuer not to be specified for valid // json. elem[jss::value] = getText(); - if (mAsset.isMPT()) - elem[jss::mpt_issuance_id] = to_string(mAsset.mptIssue()); - else - { - elem[jss::currency] = to_string(mAsset.issue().currency); - elem[jss::issuer] = to_string(mAsset.issue().account); - } + elem[jss::currency] = to_string(mIssue.currency); + elem[jss::issuer] = to_string(mIssue.account); } else { @@ -490,12 +462,6 @@ STAmount::setJson(Json::Value& elem) const } } -//------------------------------------------------------------------------------ -// -// STBase -// -//------------------------------------------------------------------------------ - SerializedTypeID STAmount::getSType() const { @@ -508,7 +474,7 @@ STAmount::getFullText() const std::string ret; ret.reserve(64); - ret = getText() + "/" + mAsset.getText(); + ret = getText() + "/" + mIssue.getText(); return ret; } @@ -528,7 +494,7 @@ STAmount::getText() const bool const scientific( (mOffset != 0) && ((mOffset < -25) || (mOffset > -5))); - if (mIsNative || mAsset.isMPT() || scientific) + if (mIsNative || scientific) { ret.append(raw_value); @@ -607,54 +573,34 @@ Json::Value STAmount::getJson(JsonOptions) const void STAmount::add(Serializer& s) const { - // TODO MPT make sure backward compatible if (mIsNative) { assert(mOffset == 0); if (!mIsNegative) - s.add64(mValue | cPositive); + s.add64(mValue | cPosNative); else s.add64(mValue); } else { - if (mAsset.isMPT()) - { - if (mIsNegative) - s.add64(mValue | cMPToken); - else - s.add64(mValue | cMPToken | cPositive); - auto const& mptIssue = mAsset.mptIssue(); - s.addBitString(mptIssue.getMptID()); - } - else - { - if (*this == beast::zero) - s.add64(cIssuedCurrency); - else if (mIsNegative) // 512 = not native - s.add64( - mValue | - (static_cast(mOffset + 512 + 97) - << (64 - 10))); - else // 256 = positive - s.add64( - mValue | - (static_cast(mOffset + 512 + 256 + 97) - << (64 - 10))); - s.addBitString(mAsset.issue().currency); - s.addBitString(mAsset.issue().account); - } + if (*this == beast::zero) + s.add64(cNotNative); + else if (mIsNegative) // 512 = not native + s.add64( + mValue | + (static_cast(mOffset + 512 + 97) << (64 - 10))); + else // 256 = positive + s.add64( + mValue | + (static_cast(mOffset + 512 + 256 + 97) + << (64 - 10))); + + s.addBitString(mIssue.currency); + s.addBitString(mIssue.account); } } -bool -STAmount::isEquivalent(const STBase& t) const -{ - const STAmount* v = dynamic_cast(&t); - return v && (*v == *this); -} - bool STAmount::isDefault() const { @@ -682,10 +628,10 @@ STAmount::isDefault() const void STAmount::canonicalize() { - if (isXRP(*this) || mAsset.isMPT()) + if (isXRP(*this)) { // native currency amounts should always have an offset of zero - mIsNative = isXRP(*this); + mIsNative = true; // log(2^64,10) ~ 19.2 if (mValue == 0 || mOffset <= -20) @@ -708,18 +654,9 @@ STAmount::canonicalize() { 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(); - } + XRPAmount xrp{num}; + mIsNegative = xrp.drops() < 0; + mValue = mIsNegative ? -xrp.drops() : xrp.drops(); mOffset = 0; } else @@ -828,7 +765,7 @@ amountFromQuality(std::uint64_t rate) } STAmount -amountFromString(Asset const& asset, std::string const& amount) +amountFromString(Issue const& issue, std::string const& amount) { static boost::regex const reNumber( "^" // the beginning of the string @@ -861,7 +798,7 @@ amountFromString(Asset const& asset, std::string const& amount) bool negative = (match[1].matched && (match[1] == "-")); // Can't specify XRP using fractional representation - if (isXRP(asset) && match[3].matched) + if (isXRP(issue) && match[3].matched) Throw("XRP must be specified in integral drops."); std::uint64_t mantissa; @@ -889,152 +826,7 @@ amountFromString(Asset const& asset, std::string const& amount) exponent += beast::lexicalCastThrow(std::string(match[7])); } - return {asset, mantissa, exponent, negative}; -} - -STAmount -amountFromJson(SField const& name, Json::Value const& v) -{ - STAmount::mantissa_type mantissa = 0; - STAmount::exponent_type exponent = 0; - bool negative = false; - Asset asset; - - 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 (!validJSONAsset(v)) - Throw("Invalid Asset'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"); - asset = xrpIssue(); - } - else - { - if (isMPT) - { - // sequence (32 bits) + account (160 bits) - uint192 u; - if (!u.parseHex(currencyOrMPTID.asString())) - Throw("invalid MPTokenIssuanceID"); - asset = u; - } - else - { - Issue issue; - if (!to_currency(issue.currency, currencyOrMPTID.asString())) - Throw("invalid currency"); - if (!issuer.isString() || - !to_issuer(issue.account, issuer.asString())) - Throw("invalid issuer"); - if (isXRP(issue)) - Throw("invalid issuer"); - asset = issue; - } - } - - 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(asset, value.asString()); - - mantissa = ret.mantissa(); - exponent = ret.exponent(); - negative = ret.negative(); - } - else - { - Throw("invalid amount type"); - } - - return {name, asset, mantissa, exponent, native, negative}; -} - -bool -amountFromJsonNoThrow(STAmount& 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; + return STAmount{issue, mantissa, exponent, negative}; } //------------------------------------------------------------------------------ @@ -1091,8 +883,7 @@ operator-(STAmount const& value) if (value.mantissa() == 0) return value; return STAmount( - value.getFName(), - value.asset(), + value.issue(), value.mantissa(), value.exponent(), value.native(), @@ -1154,20 +945,20 @@ muldiv_round( } STAmount -divide(STAmount const& num, STAmount const& den, Asset const& asset) +divide(STAmount const& num, STAmount const& den, Issue const& issue) { if (den == beast::zero) Throw("division by zero"); if (num == beast::zero) - return {asset}; + return {issue}; std::uint64_t numVal = num.mantissa(); std::uint64_t denVal = den.mantissa(); int numOffset = num.exponent(); int denOffset = den.exponent(); - if (num.native() || num.isMPT()) + if (num.native()) { while (numVal < STAmount::cMinValue) { @@ -1177,7 +968,7 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset) } } - if (den.native() || den.isMPT()) + if (den.native()) { while (denVal < STAmount::cMinValue) { @@ -1192,19 +983,19 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset) // 10^32 to 10^33) followed by a division, so the result // is in the range of 10^16 to 10^15. return STAmount( - asset, + issue, muldiv(numVal, tenTo17, denVal) + 5, numOffset - denOffset - 17, num.negative() != den.negative()); } STAmount -multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) +multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) { if (v1 == beast::zero || v2 == beast::zero) - return STAmount(asset); + return STAmount(issue); - if (v1.native() && v2.native() && isXRP(asset)) + if (v1.native() && v2.native() && isXRP(issue)) { std::uint64_t const minV = getSNValue(v1) < getSNValue(v2) ? getSNValue(v1) : getSNValue(v2); @@ -1217,36 +1008,18 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 Throw("Native value overflow"); - return STAmount(v1.getFName(), minV * maxV); - } - if (v1.isMPT() && v2.isMPT() && asset.isMPT()) - { - 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); + return STAmount(minV * maxV); } if (getSTNumberSwitchover()) - return {IOUAmount{Number{v1} * Number{v2}}, asset}; + return {IOUAmount{Number{v1} * Number{v2}}, issue}; std::uint64_t value1 = v1.mantissa(); std::uint64_t value2 = v2.mantissa(); int offset1 = v1.exponent(); int offset2 = v2.exponent(); - // TODO MPT - if (v1.native() || v1.isMPT()) + if (v1.native()) { while (value1 < STAmount::cMinValue) { @@ -1255,7 +1028,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) } } - if (v2.native() || v2.isMPT()) + if (v2.native()) { while (value2 < STAmount::cMinValue) { @@ -1269,7 +1042,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) // range. Dividing their product by 10^14 maintains the // precision, by scaling the result to 10^16 to 10^18. return STAmount( - asset, + issue, muldiv(value1, value2, tenTo14) + 7, offset1 + offset2 + 14, v1.negative() != v2.negative()); @@ -1406,15 +1179,14 @@ static STAmount mulRoundImpl( STAmount const& v1, STAmount const& v2, - Asset const& asset, + Issue const& issue, bool roundUp) { if (v1 == beast::zero || v2 == beast::zero) - return {asset}; + return {issue}; - bool const xrp = isXRP(asset); + bool const xrp = isXRP(issue); - // TODO MPT if (v1.native() && v2.native() && xrp) { std::uint64_t minV = @@ -1428,32 +1200,13 @@ mulRoundImpl( if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 Throw("Native value overflow"); - return STAmount(v1.getFName(), minV * maxV); - } - // TODO MPT - if (v1.isMPT() && v2.isMPT() && asset.isMPT()) - { - 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 STAmount(asset, minV * maxV); + return STAmount(minV * maxV); } std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa(); int offset1 = v1.exponent(), offset2 = v2.exponent(); - // TODO MPT - if (v1.native() || v1.isMPT()) + if (v1.native()) { while (value1 < STAmount::cMinValue) { @@ -1462,8 +1215,7 @@ mulRoundImpl( } } - // TODO MPT - if (v2.native() || v2.isMPT()) + if (v2.native()) { while (value2 < STAmount::cMinValue) { @@ -1494,7 +1246,7 @@ mulRoundImpl( // If appropriate, tell Number to round down. This gives the desired // result from STAmount::canonicalize. MightSaveRound const savedRound(Number::towards_zero); - return STAmount(asset, amount, offset, resultNegative); + return STAmount(issue, amount, offset, resultNegative); }(); if (roundUp && !resultNegative && !result) @@ -1511,7 +1263,7 @@ mulRoundImpl( amount = STAmount::cMinValue; offset = STAmount::cMinOffset; } - return STAmount(asset, amount, offset, resultNegative); + return STAmount(issue, amount, offset, resultNegative); } return result; } @@ -1520,22 +1272,22 @@ STAmount mulRound( STAmount const& v1, STAmount const& v2, - Asset const& asset, + Issue const& issue, bool roundUp) { return mulRoundImpl( - v1, v2, asset, roundUp); + v1, v2, issue, roundUp); } STAmount mulRoundStrict( STAmount const& v1, STAmount const& v2, - Asset const& asset, + Issue const& issue, bool roundUp) { return mulRoundImpl( - v1, v2, asset, roundUp); + v1, v2, issue, roundUp); } // We might need to use NumberRoundModeGuard. Allow the caller @@ -1545,20 +1297,19 @@ static STAmount divRoundImpl( STAmount const& num, STAmount const& den, - Asset const& asset, + Issue const& issue, bool roundUp) { if (den == beast::zero) Throw("division by zero"); if (num == beast::zero) - return {asset}; + return {issue}; std::uint64_t numVal = num.mantissa(), denVal = den.mantissa(); int numOffset = num.exponent(), denOffset = den.exponent(); - // TODO MPT - if (num.native() || num.isMPT()) + if (num.native()) { while (numVal < STAmount::cMinValue) { @@ -1567,8 +1318,7 @@ divRoundImpl( } } - // TODO MPT - if (den.native() || den.isMPT()) + if (den.native()) { while (denVal < STAmount::cMinValue) { @@ -1592,10 +1342,8 @@ divRoundImpl( int offset = numOffset - denOffset - 17; - // TODO MPT if (resultNegative != roundUp) - canonicalizeRound( - isXRP(asset) /*|| issue.isMPT()*/, amount, offset, roundUp); + canonicalizeRound(isXRP(issue), amount, offset, roundUp); STAmount result = [&]() { // If appropriate, tell Number the rounding mode we are using. @@ -1604,12 +1352,12 @@ divRoundImpl( using enum Number::rounding_mode; MightSaveRound const savedRound( roundUp ^ resultNegative ? upward : downward); - return STAmount(asset, amount, offset, resultNegative); + return STAmount(issue, amount, offset, resultNegative); }(); if (roundUp && !resultNegative && !result) { - if (isXRP(asset) || asset.isMPT()) + if (isXRP(issue)) { // return the smallest value above zero amount = 1; @@ -1621,7 +1369,7 @@ divRoundImpl( amount = STAmount::cMinValue; offset = STAmount::cMinOffset; } - return STAmount(asset, amount, offset, resultNegative); + return STAmount(issue, amount, offset, resultNegative); } return result; } @@ -1630,20 +1378,27 @@ STAmount divRound( STAmount const& num, STAmount const& den, - Asset const& asset, + Issue const& issue, bool roundUp) { - return divRoundImpl(num, den, asset, roundUp); + return divRoundImpl(num, den, issue, roundUp); } STAmount divRoundStrict( STAmount const& num, STAmount const& den, - Asset const& asset, + Issue const& issue, bool roundUp) { - return divRoundImpl(num, den, asset, roundUp); + return divRoundImpl(num, den, issue, roundUp); +} + +std::ostream& +operator<<(std::ostream& out, const STAmount& t) +{ + out << t.getFullText(); + return out; } } // namespace ripple diff --git a/src/libxrpl/protocol/STEitherAmount.cpp b/src/libxrpl/protocol/STEitherAmount.cpp new file mode 100644 index 00000000000..2fc4d3f00de --- /dev/null +++ b/src/libxrpl/protocol/STEitherAmount.cpp @@ -0,0 +1,425 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +#include + +namespace ripple { + +STEitherAmount::STEitherAmount(SerialIter& sit, SField const& name) + : STBase(name) +{ + auto const u8 = sit.peek8(); + if (((static_cast(u8) << 56) & STAmount::cNotNative) == 0 && + (u8 & STMPTAmount::cMPToken) != 0) + amount_.emplace(sit); + else + amount_.emplace(sit); +} + +STEitherAmount::STEitherAmount(XRPAmount const& amount) : amount_{amount} +{ +} + +STEitherAmount::STEitherAmount(STAmount const& amount) : amount_{amount} +{ +} + +STEitherAmount::STEitherAmount(SField const& name, STAmount const& amount) + : STBase(name), amount_{amount} +{ +} + +STEitherAmount::STEitherAmount(SField const& name, STMPTAmount const& amount) + : STBase(name), amount_{amount} +{ +} + +STEitherAmount::STEitherAmount(STMPTAmount const& amount) : amount_{amount} +{ +} + +STEitherAmount& +STEitherAmount::operator=(STAmount const& amount) +{ + amount_ = amount; + return *this; +} + +STEitherAmount& +STEitherAmount::operator=(STMPTAmount const& amount) +{ + amount_ = amount; + return *this; +} + +STEitherAmount& +STEitherAmount::operator=(XRPAmount const& amount) +{ + amount_ = amount; + return *this; +} + +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::setJson(Json::Value& jv) const +{ + std::visit([&](auto&& a) { a.setJson(jv); }, amount_); +} + +void +STEitherAmount::add(Serializer& s) const +{ + std::visit([&](auto&& a) { a.add(s); }, amount_); +} + +bool +STEitherAmount::isEquivalent(const STBase& t) const +{ + const STEitherAmount* v = dynamic_cast(&t); + return v && *this == *v; +} + +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_); +} + +bool +STEitherAmount::negative() const +{ + if (isIssue()) + return std::get(amount_).negative(); + return false; +} + +bool +STEitherAmount::native() const +{ + if (isIssue()) + return std::get(amount_).native(); + return false; +} + +STEitherAmount +STEitherAmount::zeroed() const +{ + return std::visit( + [&](auto&& a) { return STEitherAmount{a.zeroed()}; }, amount_); +} + +STEitherAmount const& +STEitherAmount::value() const +{ + return *this; +} + +std::variant const& +STEitherAmount::getValue() const +{ + return amount_; +} + +std::variant& +STEitherAmount::getValue() +{ + return amount_; +} + +AccountID +STEitherAmount::getIssuer() const +{ + if (isIssue()) + return get().getIssuer(); + return get().getIssuer(); +} + +STBase* +STEitherAmount::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STEitherAmount::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +int +STEitherAmount::signum() const noexcept +{ + return std::visit([&](auto&& a) { return a.signum(); }, amount_); +} + +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)); +} + +namespace detail { + +static 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) + MPTID 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)) + { + STAmount const ret = + amountFromString(std::get(issue), value.asString()); + mantissa = ret.mantissa(); + exponent = ret.exponent(); + negative = ret.negative(); + } + else + { + STMPTAmount const ret = + amountFromString(std::get(issue), value.asString()); + negative = ret.value() < 0; + mantissa = !negative ? ret.value() : -ret.value(); + exponent = 0; + } + } + else + { + Throw("invalid amount type"); + } + + if (std::holds_alternative(issue)) + return STEitherAmount{ + name, + STAmount{ + std::get(issue), mantissa, exponent, native, negative}}; + while (exponent-- > 0) + mantissa *= 10; + if (mantissa > maxMPTokenAmount) + Throw("MPT amount out of range"); + return STEitherAmount{ + name, STMPTAmount{std::get(issue), mantissa, negative}}; +} + +} // namespace detail + +STEitherAmount +amountFromJson(SField const& name, Json::Value const& v) +{ + return detail::amountFromJson(name, v); +} + +STAmount +amountFromJson(SF_AMOUNT const& name, Json::Value const& v) +{ + auto res = detail::amountFromJson(name, v); + if (!res.isIssue()) + Throw("Amount is not STAmount"); + return get(res); +} + +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 = get(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..c4e40e3ce36 --- /dev/null +++ b/src/libxrpl/protocol/STMPTAmount.cpp @@ -0,0 +1,228 @@ +//------------------------------------------------------------------------------ +/* + 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 + +#include + +namespace ripple { + +STMPTAmount::STMPTAmount(SerialIter& sit) +{ + auto const mask = sit.get8(); + assert((mask & cMPToken)); + if (((mask & cMPToken) == 0)) + Throw("Not MPT Amount."); + + value_ = sit.get64(); + if ((mask & cPositive) == 0) + value_ = -value_; + issue_ = sit.get192(); +} + +STMPTAmount::STMPTAmount(MPTIssue const& issue, value_type value) + : MPTAmount(value), issue_(issue) +{ +} + +STMPTAmount::STMPTAmount( + MPTIssue const& issue, + std::uint64_t value, + bool negative) + : issue_(issue) +{ + if (value > maxMPTokenAmount) + Throw("MPTAmount is out of range"); + value_ = static_cast(value); + if (negative) + value_ = -value_; +} + +STMPTAmount::STMPTAmount(value_type value) : MPTAmount(value) +{ +} + +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()); + elem[jss::value] = getText(); +} + +void +STMPTAmount::add(Serializer& s) const +{ + auto u8 = cMPToken; + if (value_ >= 0) + u8 |= cPositive; + s.add8(u8); + s.add64(value_ >= 0 ? value_ : -value_); + s.addBitString(issue_.getMptID()); +} + +bool +STMPTAmount::isDefault() const +{ + return value_ == 0 && issue_ == noMPT(); +} + +AccountID +STMPTAmount::getIssuer() const +{ + return issue_.getIssuer(); +} + +MPTID const& +STMPTAmount::getCurrency() const +{ + return issue_.getMptID(); +} + +MPTIssue const& +STMPTAmount::issue() const +{ + return issue_; +} + +void +STMPTAmount::clear() +{ + value_ = 0; +} + +void +STMPTAmount::clear(MPTIssue const& issue) +{ + issue_ = issue; + value_ = 0; +} + +STMPTAmount +STMPTAmount::zeroed() const +{ + return STMPTAmount{issue_}; +} + +int +STMPTAmount::signum() const noexcept +{ + return MPTAmount::signum(); +} + +STMPTAmount +amountFromString(MPTIssue const& issue, std::string const& amount) +{ + static boost::regex const reNumber( + "^" // the beginning of the string + "([+-]?)" // (optional) + character + "(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::uint64_t mantissa; + int exponent; + + bool negative = (match[1].matched && (match[1] == "-")); + + 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, negative}; +} + +} // namespace ripple \ No newline at end of file diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 7e62fc25bd6..ccd34d16d33 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -630,11 +630,19 @@ STObject::getFieldVL(SField const& field) const return Blob(b.data(), b.data() + b.size()); } -STAmount const& +STEitherAmount const& STObject::getFieldAmount(SField const& field) const { - static STAmount const empty{}; - return getFieldByConstRef(field, empty); + static STEitherAmount const empty{}; + return getFieldByConstRef(field, empty); +} + +STAmount const& +STObject::getFieldAmount( + TypedVariantField const& field) const +{ + static STEitherAmount const empty{}; + return get(getFieldByConstRef(field, empty)); } STPathSet const& @@ -748,7 +756,7 @@ STObject::setFieldVL(SField const& field, Slice const& s) } void -STObject::setFieldAmount(SField const& field, STAmount const& v) +STObject::setFieldAmount(SField const& field, STEitherAmount const& v) { setFieldUsingAssignment(field, v); } diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index ecc51d5275f..92044334431 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -556,8 +556,8 @@ parseLeaf( case STI_AMOUNT: try { - ret = - detail::make_stvar(amountFromJson(field, value)); + ret = detail::make_stvar( + amountFromJson(field, value)); } catch (std::exception const&) { diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index c43dac121d8..d9726ccbd06 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -143,14 +143,9 @@ STTx::getMentionedAccounts() const } else if (auto samt = dynamic_cast(&it)) { - if (samt->isIssue()) - { - auto const& issuer = samt->getIssuer(); - if (!isXRP(issuer)) - list.insert(issuer); - } - else - list.insert(samt->getIssuer()); + auto const& issuer = samt->getIssuer(); + if (!isXRP(issuer)) + list.insert(issuer); } } @@ -534,37 +529,55 @@ isMemoOkay(STObject const& st, std::string& reason) return true; } -// Ensure all account fields are 160-bits and that MPT amount is only passed -// to Payment or Clawback tx (until MPT is supported in more tx) +// Ensure all account fields are 160-bits static bool -isAccountAndMPTFieldOkay(STObject const& st) +isAccountFieldOkay(STObject const& st) { - auto const txType = st[~sfTransactionType]; - static std::unordered_set const mptAmountTx{ttPAYMENT, ttCLAWBACK}; - bool const isMPTAmountAllowed = txType && - (mptAmountTx.find(safe_cast(*txType)) != mptAmountTx.end()); for (int i = 0; i < st.getCount(); ++i) { auto t = dynamic_cast(st.peekAtPIndex(i)); if (t && t->isDefault()) return false; - auto amt = dynamic_cast(st.peekAtPIndex(i)); - if (amt && amt->isMPT() && !isMPTAmountAllowed) - return false; } return true; } +static bool +invalidMPTAmountInTx(STObject const& tx) +{ + auto const txType = tx[~sfTransactionType]; + if (!txType) + return false; + if (auto const* item = + TxFormats::getInstance().findByType(safe_cast(*txType))) + { + for (auto const& e : item->getSOTemplate()) + { + if (tx.isFieldPresent(e.sField()) && e.supportMPT() != soeMPTNone) + { + if (auto const& field = tx.peekAtField(e.sField()); + field.getSType() == STI_AMOUNT && + static_cast(field).isMPT()) + { + if (e.supportMPT() == soeMPTNotSupported) + return true; + } + } + } + } + return false; +} + bool passesLocalChecks(STObject const& st, std::string& reason) { if (!isMemoOkay(st, reason)) return false; - if (!isAccountAndMPTFieldOkay(st)) + if (!isAccountFieldOkay(st)) { - reason = "An account or MPT field is invalid."; + reason = "An account field is invalid."; return false; } @@ -573,6 +586,13 @@ passesLocalChecks(STObject const& st, std::string& reason) reason = "Cannot submit pseudo transactions."; return false; } + + if (invalidMPTAmountInTx(st)) + { + reason = "Amount can not be MPT."; + return false; + } + return true; } diff --git a/src/libxrpl/protocol/STVar.cpp b/src/libxrpl/protocol/STVar.cpp index 0cb52b5d24e..8dbe0a70550 100644 --- a/src/libxrpl/protocol/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -133,7 +133,7 @@ STVar::STVar(SerialIter& sit, SField const& name, int depth) construct(sit, name); return; case STI_AMOUNT: - construct(sit, name); + construct(sit, name); return; case STI_UINT128: construct(sit, name); @@ -200,7 +200,7 @@ STVar::STVar(SerializedTypeID id, SField const& name) construct(name); return; case STI_AMOUNT: - construct(name); + construct(name); return; case STI_UINT128: construct(name); diff --git a/src/libxrpl/protocol/Serializer.cpp b/src/libxrpl/protocol/Serializer.cpp index b99375f80dd..d9dddab74c1 100644 --- a/src/libxrpl/protocol/Serializer.cpp +++ b/src/libxrpl/protocol/Serializer.cpp @@ -358,6 +358,15 @@ SerialIter::skip(int length) remain_ -= length; } +unsigned char +SerialIter::peek8() +{ + if (remain_ < 1) + Throw("invalid SerialIter peek8"); + unsigned char t = *p_; + return t; +} + unsigned char SerialIter::get8() { diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 4796dd52236..950163e3aa5 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -190,7 +190,6 @@ transResults() MAKE_ERROR(temEMPTY_DID, "Malformed: No DID data provided."), MAKE_ERROR(temINVALID, "The transaction is ill-formed."), MAKE_ERROR(temINVALID_FLAG, "The transaction has an invalid flag."), - MAKE_ERROR(temMPT_NOT_SUPPORTED, "MPT is not supported."), MAKE_ERROR(temREDUNDANT, "The transaction is redundant."), MAKE_ERROR(temRIPPLE_EMPTY, "PathSet with no paths."), MAKE_ERROR(temUNCERTAIN, "In process of determining result. Never returned."), diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index d67fa34ab0a..92e8ff3b690 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -162,7 +162,7 @@ TxFormats::TxFormats() ttPAYMENT, { {sfDestination, soeREQUIRED}, - {sfAmount, soeREQUIRED}, + {sfAmount, soeREQUIRED, soeMPTSupported}, {sfSendMax, soeOPTIONAL}, {sfPaths, soeDEFAULT}, {sfInvoiceID, soeOPTIONAL}, @@ -377,7 +377,7 @@ TxFormats::TxFormats() add(jss::Clawback, ttCLAWBACK, { - {sfAmount, soeREQUIRED}, + {sfAmount, soeREQUIRED, soeMPTSupported}, {sfMPTokenHolder, soeOPTIONAL}, }, commonFields); diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index 253d00e8414..85ed5dc487e 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -144,7 +144,7 @@ TxMeta::getAffectedAccounts() const (field.getFName() == sfTakerPays) || (field.getFName() == sfTakerGets)) { - auto lim = dynamic_cast(&field); + auto lim = dynamic_cast(&field); assert(lim); if (lim != nullptr) diff --git a/src/libxrpl/protocol/UintTypes.cpp b/src/libxrpl/protocol/UintTypes.cpp index 9487e5dcbf7..e50e52c1c30 100644 --- a/src/libxrpl/protocol/UintTypes.cpp +++ b/src/libxrpl/protocol/UintTypes.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -125,11 +126,11 @@ noCurrency() return currency; } -MPT const& +MPTID const& noMPT() { - static MPT const mpt{0, noAccount()}; - return mpt; + static MPTID const id = getMptID(noAccount(), 0); + return id; } Currency const& diff --git a/src/libxrpl/protocol/XChainAttestations.cpp b/src/libxrpl/protocol/XChainAttestations.cpp index 82e73445693..2d736ebff43 100644 --- a/src/libxrpl/protocol/XChainAttestations.cpp +++ b/src/libxrpl/protocol/XChainAttestations.cpp @@ -107,7 +107,7 @@ AttestationBase::AttestationBase(STObject const& o) , publicKey{o[sfPublicKey]} , signature{o[sfSignature]} , sendingAccount{o[sfAccount]} - , sendingAmount{o[sfAmount]} + , sendingAmount{get(o[sfAmount])} , rewardAccount{o[sfAttestationRewardAccount]} , wasLockingChainSend{bool(o[sfWasLockingChainSend])} { @@ -132,7 +132,7 @@ AttestationBase::addHelper(STObject& o) const o[sfAttestationSignerAccount] = attestationSignerAccount; o[sfPublicKey] = publicKey; o[sfSignature] = signature; - o[sfAmount] = sendingAmount; + o[sfAmount] = STEitherAmount{sendingAmount}; o[sfAccount] = sendingAccount; o[sfAttestationRewardAccount] = rewardAccount; o[sfWasLockingChainSend] = wasLockingChainSend; @@ -216,7 +216,7 @@ std::vector AttestationClaim::message( STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, + STEitherAmount const& sendingAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t claimID, @@ -361,8 +361,8 @@ std::vector AttestationCreateAccount::message( STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, - STAmount const& rewardAmount, + STEitherAmount const& sendingAmount, + STEitherAmount const& rewardAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, @@ -371,7 +371,7 @@ AttestationCreateAccount::message( STObject o{sfGeneric}; // Serialize in SField order to make python serializers easier to write o[sfXChainAccountCreateCount] = createCount; - o[sfAmount] = sendingAmount; + o[sfAmount] = STEitherAmount{sendingAmount}; o[sfSignatureReward] = rewardAmount; o[sfDestination] = dst; o[sfOtherChainSource] = sendingAccount; @@ -438,7 +438,7 @@ XChainClaimAttestation::XChainClaimAttestation( std::optional const& dst_) : keyAccount(keyAccount_) , publicKey(publicKey_) - , amount(sfAmount, amount_) + , amount(amount_) , rewardAccount(rewardAccount_) , wasLockingChainSend(wasLockingChainSend_) , dst(dst_) @@ -466,7 +466,7 @@ XChainClaimAttestation::XChainClaimAttestation(STObject const& o) : XChainClaimAttestation{ o[sfAttestationSignerAccount], PublicKey{o[sfPublicKey]}, - o[sfAmount], + get(o[sfAmount]), o[sfAttestationRewardAccount], o[sfWasLockingChainSend] != 0, o[~sfDestination]} {}; @@ -503,7 +503,7 @@ XChainClaimAttestation::toSTObject() const o[sfAttestationSignerAccount] = STAccount{sfAttestationSignerAccount, keyAccount}; o[sfPublicKey] = publicKey; - o[sfAmount] = STAmount{sfAmount, amount}; + o[sfAmount] = STEitherAmount(amount); o[sfAttestationRewardAccount] = STAccount{sfAttestationRewardAccount, rewardAccount}; o[sfWasLockingChainSend] = wasLockingChainSend; @@ -563,8 +563,8 @@ XChainCreateAccountAttestation::XChainCreateAccountAttestation( AccountID const& dst_) : keyAccount(keyAccount_) , publicKey(publicKey_) - , amount(sfAmount, amount_) - , rewardAmount(sfSignatureReward, rewardAmount_) + , amount(amount_) + , rewardAmount(rewardAmount_) , rewardAccount(rewardAccount_) , wasLockingChainSend(wasLockingChainSend_) , dst(dst_) @@ -576,7 +576,7 @@ XChainCreateAccountAttestation::XChainCreateAccountAttestation( : XChainCreateAccountAttestation{ o[sfAttestationSignerAccount], PublicKey{o[sfPublicKey]}, - o[sfAmount], + get(o[sfAmount]), o[sfSignatureReward], o[sfAttestationRewardAccount], o[sfWasLockingChainSend] != 0, @@ -616,8 +616,8 @@ XChainCreateAccountAttestation::toSTObject() const o[sfAttestationSignerAccount] = STAccount{sfAttestationSignerAccount, keyAccount}; o[sfPublicKey] = publicKey; - o[sfAmount] = STAmount{sfAmount, amount}; - o[sfSignatureReward] = STAmount{sfSignatureReward, rewardAmount}; + o[sfAmount] = STEitherAmount{sfAmount, amount}; + o[sfSignatureReward] = STAmount{rewardAmount}; o[sfAttestationRewardAccount] = STAccount{sfAttestationRewardAccount, rewardAccount}; o[sfWasLockingChainSend] = wasLockingChainSend; diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 96053b93b44..fc53ba3f016 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -1547,8 +1547,7 @@ struct AMMExtended_test : public jtx::AMMTest Env env = pathTestEnv(); fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All); AMM ammCharlie(env, charlie, XRP(10), USD(11)); - auto [st, sa, da] = - find_paths(env, alice, bob, USD(-1), XRP(1).value()); + auto [st, sa, da] = find_paths(env, alice, bob, USD(-1), XRP(1)); BEAST_EXPECT(sa == XRP(1)); BEAST_EXPECT(equal(da, USD(1))); if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) @@ -1565,8 +1564,7 @@ struct AMMExtended_test : public jtx::AMMTest fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All); AMM ammCharlie(env, charlie, XRP(11), USD(10)); env.close(); - auto [st, sa, da] = - find_paths(env, alice, bob, drops(-1), USD(1).value()); + auto [st, sa, da] = find_paths(env, alice, bob, drops(-1), USD(1)); BEAST_EXPECT(sa == USD(1)); BEAST_EXPECT(equal(da, XRP(1))); if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) @@ -1916,7 +1914,7 @@ struct AMMExtended_test : public jtx::AMMTest sendmax(EUR(500)), txflags(tfNoRippleDirect | tfPartialPayment)); - auto const carolUSD = env.balance(carol, USD).value(); + auto const carolUSD = get(env.balance(carol, USD)); BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50)); } diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 31b45abf43a..cb82bef9b59 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -211,7 +211,7 @@ class Check_test : public beast::unit_test::suite Env env{*this, features}; - STAmount const startBalance{XRP(1000).value()}; + STAmount const startBalance{XRP(1000)}; env.fund(startBalance, gw, alice, bob); // Note that no trust line has been set up for alice, but alice can @@ -321,7 +321,7 @@ class Check_test : public beast::unit_test::suite Env env{*this, features | disallowIncoming}; - STAmount const startBalance{XRP(1000).value()}; + STAmount const startBalance{XRP(1000)}; env.fund(startBalance, gw, alice, bob); /* @@ -405,7 +405,7 @@ class Check_test : public beast::unit_test::suite Env env{*this, features}; - STAmount const startBalance{XRP(1000).value()}; + STAmount const startBalance{XRP(1000)}; env.fund(startBalance, gw1, gwF, alice, bob); // Bad fee. @@ -587,7 +587,7 @@ class Check_test : public beast::unit_test::suite Env env{*this, features}; XRPAmount const baseFeeDrops{env.current()->fees().base}; - STAmount const startBalance{XRP(300).value()}; + STAmount const startBalance{XRP(300)}; env.fund(startBalance, alice, bob); { // Basic XRP check. @@ -1192,8 +1192,8 @@ class Check_test : public beast::unit_test::suite double pct, double amount) { // Capture bob's and alice's balances so we can test at the end. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; - STAmount const bobStart{env.balance(bob, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD.issue())}; + STAmount const bobStart{env.balance(bob, USD.issue())}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); @@ -1217,8 +1217,8 @@ class Check_test : public beast::unit_test::suite double pct, double amount) { // Capture bob's and alice's balances so we can test at the end. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; - STAmount const bobStart{env.balance(bob, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD.issue())}; + STAmount const bobStart{env.balance(bob, USD.issue())}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); @@ -1281,7 +1281,7 @@ class Check_test : public beast::unit_test::suite double max2) { // Capture alice's balance so we can test at the end. It doesn't // make any sense to look at the balance of a gateway. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD.issue())}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); @@ -1314,7 +1314,7 @@ class Check_test : public beast::unit_test::suite double max2) { // Capture alice's balance so we can test at the end. It doesn't // make any sense to look at the balance of the issuer. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD.issue())}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 9d1257d16bf..ee11f5e4747 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -763,7 +763,7 @@ struct Flow_test : public beast::unit_test::suite sendmax(EUR(500)), txflags(tfNoRippleDirect | tfPartialPayment)); - auto const carolUSD = env.balance(carol, USD).value(); + auto const carolUSD = get(env.balance(carol, USD)); BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50)); } diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 6e6ccebb1c9..d534b24320a 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -345,7 +346,8 @@ class MPToken_test : public beast::unit_test::suite env, alice, {.holders = {&bob}, - .xrpHolders = acctReserve + XRP(1).value().xrp()}); + .xrpHolders = + acctReserve + get(XRP(1).value()).xrp()}); mptAlice1.create(); MPTTester mptAlice2(env, alice, {.fund = false}); @@ -647,6 +649,25 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(bob, carol, 50); } + // Holder is not authorized + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {&bob, &carol}}); + + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + + // issuer to holder + mptAlice.pay(alice, bob, 100, tecNO_AUTH); + + // holder to issuer + mptAlice.pay(bob, alice, 100, tecNO_AUTH); + + // holder to holder + mptAlice.pay(bob, carol, 50, tecNO_AUTH); + } + // If allowlisting is enabled, Payment fails if the receiver is not // authorized { @@ -774,25 +795,52 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(alice, bob, 1, tecMPT_MAX_AMOUNT_EXCEEDED); } - // TODO: This test is currently failing! Modify the STAmount to change - // the range // Issuer fails trying to send more than the default maximum // amount allowed - // { - // Env env{*this, features}; + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {&bob}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = &bob}); + + // issuer sends holder the default max amount allowed + mptAlice.pay(alice, bob, maxMPTokenAmount); + + // issuer tries to exceed max amount + mptAlice.pay(alice, bob, 1, tecMPT_MAX_AMOUNT_EXCEEDED); + } + + // Can't pay negative amount + { + Env env{*this, features}; - // MPTTester mptAlice(env, alice, {.holders = {&bob}}); + MPTTester mptAlice(env, alice, {.holders = {&bob}}); - // mptAlice.create({.ownerCount = 1, .holderCount = 0}); + mptAlice.create({.ownerCount = 1, .holderCount = 0}); - // mptAlice.authorize({.account = &bob}); + mptAlice.authorize({.account = &bob}); - // // issuer sends holder the default max amount allowed - // mptAlice.pay(alice, bob, maxMPTokenAmount); + mptAlice.pay(alice, bob, -1, temBAD_AMOUNT); + } - // // issuer tries to exceed max amount - // mptAlice.pay(alice, bob, 1, tecMPT_MAX_AMOUNT_EXCEEDED); - // } + // pay more than max amount + // fails in the json parser before + // transactor is called + { + Env env{*this, features}; + env.fund(XRP(1'000), alice, bob); + STMPTAmount mpt{MPTIssue{getMptID(alice.id(), 1)}, UINT64_C(100)}; + Json::Value jv; + jv[jss::secret] = alice.name(); + jv[jss::tx_json] = pay(alice, bob, mpt); + jv[jss::tx_json][jss::Amount][jss::value] = + to_string(maxMPTokenAmount + 1); + auto const jrr = env.rpc("json", "submit", to_string(jv)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + } // Transfer fee { @@ -854,6 +902,84 @@ class MPToken_test : public beast::unit_test::suite // bob can send back to alice(issuer) just fine mptAlice.pay(bob, alice, 10); } + + // MPT is disabled + { + Env env{*this, features - featureMPTokensV1}; + Account const alice("alice"); + Account const bob("bob"); + + env.fund(XRP(1'000), alice); + env.fund(XRP(1'000), bob); + STMPTAmount mpt{MPTIssue{getMptID(alice.id(), 1)}, UINT64_C(100)}; + + env(pay(alice, bob, mpt), ter(temDISABLED)); + } + + // MPT is disabled, unsigned request + { + Env env{*this, features - featureMPTokensV1}; + Account const alice("alice"); // issuer + Account const carol("carol"); + auto const USD = alice["USD"]; + + env.fund(XRP(1'000), alice); + env.fund(XRP(1'000), carol); + STMPTAmount mpt{MPTIssue{getMptID(alice.id(), 1)}, UINT64_C(100)}; + + Json::Value jv; + jv[jss::secret] = alice.name(); + jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base); + jv[jss::tx_json] = pay(alice, carol, mpt); + auto const jrr = env.rpc("json", "submit", to_string(jv)); + BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "temDISABLED"); + } + + // Invalid combination of send, sendMax, deliverMin + { + Env env{*this, features}; + Account const alice("alice"); + Account const carol("carol"); + + MPTTester mptAlice(env, alice, {.holders = {&carol}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = &carol}); + + // sendMax and DeliverMin are valid XRP amount, + // but is invalid combination with MPT amount + env(pay(alice, carol, mptAlice.mpt(100)), + sendmax(XRP(100)), + ter(temMALFORMED)); + env(pay(alice, carol, mptAlice.mpt(100)), + delivermin(XRP(100)), + ter(temMALFORMED)); + } + + // build_path is invalid if MPT + { + Env env{*this, features}; + Account const alice("alice"); + Account const carol("carol"); + + MPTTester mptAlice(env, alice, {.holders = {&bob, &carol}}); + + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + + mptAlice.authorize({.account = &carol}); + + Json::Value payment; + payment[jss::secret] = alice.name(); + payment[jss::tx_json] = pay(alice, carol, mptAlice.mpt(100)); + + payment[jss::build_path] = true; + auto jrr = env.rpc("json", "submit", to_string(payment)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + BEAST_EXPECT( + jrr[jss::result][jss::error_message] == + "Field 'build_path' not allowed in this context."); + } } void @@ -861,17 +987,309 @@ class MPToken_test : public beast::unit_test::suite { testcase("MPT Amount Invalid in Transaction"); using namespace test::jtx; - Env env{*this, features}; - Account const alice("alice"); // issuer - - MPTTester mptAlice(env, alice); - mptAlice.create(); - - env(offer(alice, mptAlice.mpt(100), XRP(100)), ter(telENV_RPC_FAILED)); - env.close(); + std::set txWithAmounts; + for (auto const& format : TxFormats::getInstance()) + { + for (auto const& e : format.getSOTemplate()) + { + // Transaction has amount fields. + // Exclude Clawback, which only supports sfAmount and is checked + // in the transactor for amendment enable/disable. Exclude + // pseudo-transaction SetFee. Don't consider the Fee field since + // it's included in every transaction. + if (e.supportMPT() != soeMPTNone && + e.sField().getName() != jss::Fee && + format.getName() != jss::Clawback && + format.getName() != jss::SetFee) + { + txWithAmounts.insert(format.getName()); + break; + } + } + } - BEAST_EXPECT(expectOffers(env, alice, 0)); + Account const alice("alice"); + auto const USD = alice["USD"]; + Account const carol("carol"); + MPTIssue issue(getMptID(alice.id(), 1)); + STMPTAmount mpt{issue, UINT64_C(100)}; + auto const jvb = bridge(alice, USD, alice, USD); + for (auto const& feature : {features, features - featureMPTokensV1}) + { + Env env{*this, feature}; + env.fund(XRP(1'000), alice); + env.fund(XRP(1'000), carol); + auto test = [&](Json::Value const& jv) { + txWithAmounts.erase(jv[jss::TransactionType].asString()); + + // tx is signed + auto jtx = env.jt(jv); + Serializer s; + jtx.stx->add(s); + auto jrr = env.rpc("submit", strHex(s.slice())); + BEAST_EXPECT( + jrr[jss::result][jss::error] == "invalidTransaction"); + + // tx is unsigned + Json::Value jv1; + jv1[jss::secret] = alice.name(); + jv1[jss::tx_json] = jv; + jrr = env.rpc("json", "submit", to_string(jv1)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + }; + // All transactions with sfAmount, which don't support MPT + // and transactions with amount fields, which can't be MPT + + // AMMCreate + auto ammCreate = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMCreate; + jv[jss::Account] = alice.human(); + jv[jss::Amount] = (field.fieldName == sfAmount.fieldName) + ? mpt.getJson(JsonOptions::none) + : "100000000"; + jv[jss::Amount2] = (field.fieldName == sfAmount2.fieldName) + ? mpt.getJson(JsonOptions::none) + : "100000000"; + jv[jss::TradingFee] = 0; + test(jv); + }; + ammCreate(sfAmount); + ammCreate(sfAmount2); + // AMMDeposit + auto ammDeposit = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMDeposit; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = to_json(xrpIssue()); + jv[jss::Asset2] = to_json(USD.issue()); + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + jv[jss::Flags] = tfSingleAsset; + test(jv); + }; + ammDeposit(sfAmount); + for (SField const& field : + {std::ref(sfAmount2), + std::ref(sfEPrice), + std::ref(sfLPTokenOut)}) + ammDeposit(field); + // AMMWithdraw + auto ammWithdraw = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMWithdraw; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = to_json(xrpIssue()); + jv[jss::Asset2] = to_json(USD.issue()); + jv[jss::Flags] = tfSingleAsset; + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv); + }; + ammWithdraw(sfAmount); + for (SField const& field : + {std::ref(sfAmount2), + std::ref(sfEPrice), + std::ref(sfLPTokenIn)}) + ammWithdraw(field); + // AMMBid + auto ammBid = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMBid; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = to_json(xrpIssue()); + jv[jss::Asset2] = to_json(USD.issue()); + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv); + }; + ammBid(sfBidMin); + ammBid(sfBidMax); + // CheckCash + auto checkCash = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::CheckCash; + jv[jss::Account] = alice.human(); + jv[sfCheckID.fieldName] = to_string(uint256{1}); + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv); + }; + checkCash(sfAmount); + checkCash(sfDeliverMin); + // CheckCreate + { + Json::Value jv; + jv[jss::TransactionType] = jss::CheckCreate; + jv[jss::Account] = alice.human(); + jv[jss::Destination] = carol.human(); + jv[jss::SendMax] = mpt.getJson(JsonOptions::none); + test(jv); + } + // EscrowCreate + { + Json::Value jv; + jv[jss::TransactionType] = jss::EscrowCreate; + jv[jss::Account] = alice.human(); + jv[jss::Destination] = carol.human(); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv); + } + // OfferCreate + { + Json::Value const jv = offer(alice, USD(100), mpt); + test(jv); + } + // PaymentChannelCreate + { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelCreate; + jv[jss::Account] = alice.human(); + jv[jss::Destination] = carol.human(); + jv[jss::SettleDelay] = 1; + jv[sfPublicKey.fieldName] = strHex(alice.pk().slice()); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv); + } + // PaymentChannelFund + { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelFund; + jv[jss::Account] = alice.human(); + jv[sfChannel.fieldName] = to_string(uint256{1}); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv); + } + // PaymentChannelClaim + { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelClaim; + jv[jss::Account] = alice.human(); + jv[sfChannel.fieldName] = to_string(uint256{1}); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv); + } + // Payment + auto payment = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::Payment; + jv[jss::Account] = alice.human(); + jv[jss::Destination] = carol.human(); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + if (field == sfSendMax) + jv[jss::SendMax] = mpt.getJson(JsonOptions::none); + else + jv[jss::DeliverMin] = mpt.getJson(JsonOptions::none); + test(jv); + }; + payment(sfSendMax); + payment(sfDeliverMin); + // NFTokenCreateOffer + { + Json::Value jv; + jv[jss::TransactionType] = jss::NFTokenCreateOffer; + jv[jss::Account] = alice.human(); + jv[sfNFTokenID.fieldName] = to_string(uint256{1}); + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv); + } + // NFTokenAcceptOffer + { + Json::Value jv; + jv[jss::TransactionType] = jss::NFTokenAcceptOffer; + jv[jss::Account] = alice.human(); + jv[sfNFTokenBrokerFee.fieldName] = + mpt.getJson(JsonOptions::none); + test(jv); + } + // NFTokenMint + { + Json::Value jv; + jv[jss::TransactionType] = jss::NFTokenMint; + jv[jss::Account] = alice.human(); + jv[sfNFTokenTaxon.fieldName] = 1; + jv[jss::Amount] = mpt.getJson(JsonOptions::none); + test(jv); + } + // TrustSet + auto trustSet = [&](SField const& field) { + Json::Value jv; + jv[jss::TransactionType] = jss::TrustSet; + jv[jss::Account] = alice.human(); + jv[jss::Flags] = 0; + jv[field.fieldName] = mpt.getJson(JsonOptions::none); + test(jv); + }; + trustSet(sfLimitAmount); + trustSet(sfFee); + // XChainCommit + { + Json::Value const jv = xchain_commit(alice, jvb, 1, mpt); + test(jv); + } + // XChainClaim + { + Json::Value const jv = xchain_claim(alice, jvb, 1, mpt, alice); + test(jv); + } + // XChainCreateClaimID + { + Json::Value const jv = + xchain_create_claim_id(alice, jvb, mpt, alice); + test(jv); + } + // XChainAddClaimAttestation + { + Json::Value const jv = claim_attestation( + alice, + jvb, + alice, + mpt, + alice, + true, + 1, + alice, + signer(alice)); + test(jv); + } + // XChainAddAccountCreateAttestation + { + Json::Value const jv = create_account_attestation( + alice, + jvb, + alice, + mpt, + XRP(10), + alice, + false, + 1, + alice, + signer(alice)); + test(jv); + } + // XChainAccountCreateCommit + { + Json::Value const jv = sidechain_xchain_account_create( + alice, jvb, alice, mpt, XRP(10)); + test(jv); + } + // XChain[Create|Modify]Bridge + auto bridgeTx = [&](Json::StaticString const& tt, + bool minAmount = false) { + Json::Value jv; + jv[jss::TransactionType] = tt; + jv[jss::Account] = alice.human(); + jv[sfXChainBridge.fieldName] = jvb; + jv[sfSignatureReward.fieldName] = + mpt.getJson(JsonOptions::none); + if (minAmount) + jv[sfMinAccountCreateAmount.fieldName] = + mpt.getJson(JsonOptions::none); + test(jv); + }; + bridgeTx(jss::XChainCreateBridge); + bridgeTx(jss::XChainCreateBridge, true); + bridgeTx(jss::XChainModifyBridge); + bridgeTx(jss::XChainModifyBridge, true); + } + BEAST_EXPECT(txWithAmounts.empty()); } void @@ -918,9 +1336,9 @@ class MPToken_test : public beast::unit_test::suite auto const USD = alice["USD"]; auto const mpt = ripple::test::jtx::MPT( - alice.name(), std::make_pair(env.seq(alice), alice.id())); + alice.name(), getMptID(alice.id(), env.seq(alice))); - env(claw(alice, bob["USD"](5), bob), ter(temDISABLED)); + env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED)); env.close(); env(claw(alice, mpt(5)), ter(temDISABLED)); @@ -941,7 +1359,7 @@ class MPToken_test : public beast::unit_test::suite auto const USD = alice["USD"]; auto const mpt = ripple::test::jtx::MPT( - alice.name(), std::make_pair(env.seq(alice), alice.id())); + alice.name(), getMptID(alice.id(), env.seq(alice))); // clawing back IOU from a MPT holder fails env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED)); @@ -959,9 +1377,9 @@ class MPToken_test : public beast::unit_test::suite env(claw(alice, mpt(5), alice), ter(temMALFORMED)); env.close(); - // TODO: uncomment after stamount changes - // env(claw(alice, mpt(maxMPTokenAmount), bob), ter(temBAD_AMOUNT)); - // env.close(); + // can't clawback negative amount + env(claw(alice, mpt(-1), bob), ter(temBAD_AMOUNT)); + env.close(); } // Preclaim - clawback fails when MPTCanClawback is disabled on issuance @@ -1000,7 +1418,7 @@ class MPToken_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {&bob}}); auto const fakeMpt = ripple::test::jtx::MPT( - alice.name(), std::make_pair(env.seq(alice), alice.id())); + alice.name(), getMptID(alice.id(), env.seq(alice))); // issuer tries to clawback MPT where issuance doesn't exist env(claw(alice, fakeMpt(5), bob), ter(tecOBJECT_NOT_FOUND)); @@ -1026,6 +1444,29 @@ class MPToken_test : public beast::unit_test::suite // issuer mptAlice.claw(carol, bob, 1, tecNO_PERMISSION); } + + // clawback more than max amount + // fails in the json parser before + // transactor is called + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const mpt = ripple::test::jtx::MPT( + alice.name(), getMptID(alice.id(), env.seq(alice))); + + Json::Value jv = claw(alice, mpt(1), bob); + jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1); + Json::Value jv1; + jv1[jss::secret] = alice.name(); + jv1[jss::tx_json] = jv; + auto const jrr = env.rpc("json", "submit", to_string(jv1)); + BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); + } } void @@ -1171,15 +1612,10 @@ class MPToken_test : public beast::unit_test::suite // Test Direct Payment testPayment(all); - // Test MPT Amount is invalid in non-Payment Tx + // Test MPT Amount is invalid in Tx, which don't support MPT testMPTInvalidInTx(all); // Test parsed MPTokenIssuanceID in API response metadata - // TODO: This test exercises the parsing logic of mptID in `tx`, - // but, - // mptID is also parsed in different places like `account_tx`, - // `subscribe`, `ledger`. We should create test for these - // occurances (lower prioirity). testTxJsonMetaFields(all); } }; diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 2b4245a1ae4..01608b5b805 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -1876,8 +1876,7 @@ class OfferBaseUtil_test : public beast::unit_test::suite jrr = ledgerEntryRoot(env, bob); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP(10000).value().mantissa() + XRP(250).value().mantissa())); + std::to_string((XRP(10000) + XRP(250)).value().mantissa())); auto jro = ledgerEntryOffer(env, carol, carolOfferSeq); BEAST_EXPECT( @@ -2302,12 +2301,12 @@ class OfferBaseUtil_test : public beast::unit_test::suite jtx::Account const& account, jtx::PrettyAmount const& expectBalance) { - auto const sleTrust = - env.le(keylet::line(account.id(), expectBalance.value().issue())); + auto const sleTrust = env.le( + keylet::line(account.id(), get(expectBalance).issue())); BEAST_EXPECT(sleTrust); if (sleTrust) { - Issue const issue = expectBalance.value().issue(); + Issue const issue = get(expectBalance).issue(); bool const accountLow = account.id() < issue.account; STAmount low{issue}; @@ -5134,12 +5133,12 @@ class OfferBaseUtil_test : public beast::unit_test::suite env(pay(issuer, maker, EUR(1'000))); env.close(); - auto makerUSDBalance = env.balance(maker, USD).value(); - auto takerUSDBalance = env.balance(taker, USD).value(); - auto makerEURBalance = env.balance(maker, EUR).value(); - auto takerEURBalance = env.balance(taker, EUR).value(); - auto makerXRPBalance = env.balance(maker, XRP).value(); - auto takerXRPBalance = env.balance(taker, XRP).value(); + auto makerUSDBalance = get(env.balance(maker, USD)); + auto takerUSDBalance = get(env.balance(taker, USD)); + auto makerEURBalance = get(env.balance(maker, EUR)); + auto takerEURBalance = get(env.balance(taker, EUR)); + auto makerXRPBalance = get(env.balance(maker, XRP)); + auto takerXRPBalance = get(env.balance(taker, XRP)); // tfFillOrKill, TakerPays must be filled { @@ -5162,8 +5161,8 @@ class OfferBaseUtil_test : public beast::unit_test::suite { makerUSDBalance -= USD(100); takerUSDBalance += USD(100); - makerXRPBalance += XRP(100).value(); - takerXRPBalance -= XRP(100).value(); + makerXRPBalance += XRP(100); + takerXRPBalance -= XRP(100); } BEAST_EXPECT(expectOffers(env, taker, 0)); @@ -5181,8 +5180,8 @@ class OfferBaseUtil_test : public beast::unit_test::suite { makerUSDBalance += USD(100); takerUSDBalance -= USD(100); - makerXRPBalance -= XRP(100).value(); - takerXRPBalance += XRP(100).value(); + makerXRPBalance -= XRP(100); + takerXRPBalance += XRP(100); } BEAST_EXPECT(expectOffers(env, taker, 0)); @@ -5217,8 +5216,8 @@ class OfferBaseUtil_test : public beast::unit_test::suite makerUSDBalance -= USD(101); takerUSDBalance += USD(101); - makerXRPBalance += XRP(101).value() - txfee(env, 1); - takerXRPBalance -= XRP(101).value() + txfee(env, 1); + makerXRPBalance += XRP(101) - txfee(env, 1); + takerXRPBalance -= XRP(101) + txfee(env, 1); BEAST_EXPECT(expectOffers(env, taker, 0)); env(offer(maker, USD(101), XRP(101))); @@ -5230,8 +5229,8 @@ class OfferBaseUtil_test : public beast::unit_test::suite makerUSDBalance += USD(101); takerUSDBalance -= USD(101); - makerXRPBalance -= XRP(101).value() + txfee(env, 1); - takerXRPBalance += XRP(101).value() - txfee(env, 1); + makerXRPBalance -= XRP(101) + txfee(env, 1); + takerXRPBalance += XRP(101) - txfee(env, 1); BEAST_EXPECT(expectOffers(env, taker, 0)); env(offer(maker, USD(101), EUR(101))); diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 1db15388ff0..cb0f031a360 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -197,7 +197,8 @@ class Path_test : public beast::unit_test::suite STAmount da; if (result.isMember(jss::destination_amount)) - da = amountFromJson(sfGeneric, result[jss::destination_amount]); + da = get( + amountFromJson(sfGeneric, result[jss::destination_amount])); STAmount sa; STPathSet paths; @@ -209,11 +210,12 @@ class Path_test : public beast::unit_test::suite auto const& path = alts[0u]; if (path.isMember(jss::source_amount)) - sa = amountFromJson(sfGeneric, path[jss::source_amount]); + sa = get( + amountFromJson(sfGeneric, path[jss::source_amount])); if (path.isMember(jss::destination_amount)) - da = amountFromJson( - sfGeneric, path[jss::destination_amount]); + da = get(amountFromJson( + sfGeneric, path[jss::destination_amount])); if (path.isMember(jss::paths_computed)) { @@ -1244,8 +1246,7 @@ class Path_test : public beast::unit_test::suite env.close(); env(offer(charlie, XRP(10), USD(10))); env.close(); - auto [st, sa, da] = - find_paths(env, alice, bob, USD(-1), XRP(100).value()); + auto [st, sa, da] = find_paths(env, alice, bob, USD(-1), XRP(100)); BEAST_EXPECT(sa == XRP(10)); BEAST_EXPECT(equal(da, USD(10))); if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) @@ -1268,7 +1269,7 @@ class Path_test : public beast::unit_test::suite env(offer(charlie, USD(10), XRP(10))); env.close(); auto [st, sa, da] = - find_paths(env, alice, bob, drops(-1), USD(100).value()); + find_paths(env, alice, bob, drops(-1), USD(100)); BEAST_EXPECT(sa == USD(10)); BEAST_EXPECT(equal(da, XRP(10))); if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index e49e5cbd6dc..dedf6fe0bc8 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -65,7 +65,7 @@ struct PayChan_test : public beast::unit_test::suite auto const slep = view.read({ltPAYCHAN, chan}); if (!slep) return XRPAmount{-1}; - return (*slep)[sfAmount]; + return get((*slep)[sfAmount]); } static std::optional @@ -137,9 +137,9 @@ struct PayChan_test : public beast::unit_test::suite { // No signature claim with bad amounts (negative and non-xrp) - auto const iou = USDA(100).value(); - auto const negXRP = XRP(-100).value(); - auto const posXRP = XRP(100).value(); + auto const iou = USDA(100); + auto const negXRP = XRP(-100); + auto const posXRP = XRP(100); env(claim(alice, chan, iou, iou), ter(temBAD_AMOUNT)); env(claim(alice, chan, posXRP, iou), ter(temBAD_AMOUNT)); env(claim(alice, chan, iou, posXRP), ter(temBAD_AMOUNT)); @@ -215,13 +215,7 @@ struct PayChan_test : public beast::unit_test::suite { // Wrong signing key auto const sig = signClaimAuth(bob.pk(), bob.sk(), chan, XRP(1500)); - env(claim( - bob, - chan, - XRP(1500).value(), - XRP(1500).value(), - Slice(sig), - bob.pk()), + env(claim(bob, chan, XRP(1500), XRP(1500), Slice(sig), bob.pk()), ter(temBAD_SIGNER)); BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); @@ -229,13 +223,7 @@ struct PayChan_test : public beast::unit_test::suite { // Bad signature auto const sig = signClaimAuth(bob.pk(), bob.sk(), chan, XRP(1500)); - env(claim( - bob, - chan, - XRP(1500).value(), - XRP(1500).value(), - Slice(sig), - alice.pk()), + env(claim(bob, chan, XRP(1500), XRP(1500), Slice(sig), alice.pk()), ter(temBAD_SIGNATURE)); BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); @@ -551,7 +539,7 @@ struct PayChan_test : public beast::unit_test::suite { // claim the entire amount auto const preBob = env.balance(bob); - env(claim(alice, chan, channelFunds.value(), channelFunds.value())); + env(claim(alice, chan, channelFunds, channelFunds)); BEAST_EXPECT(channelBalance(*env.current(), chan) == channelFunds); BEAST_EXPECT(env.balance(bob) == preBob + channelFunds); } @@ -659,7 +647,7 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelExists(*env.current(), chan)); env(fset(bob, asfDisallowXRP)); - auto const reqBal = XRP(500).value(); + auto const reqBal = XRP(500); env(claim(alice, chan, reqBal, reqBal), ter(tecNO_TARGET)); } { @@ -673,7 +661,7 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelExists(*env.current(), chan)); env(fset(bob, asfDisallowXRP)); - auto const reqBal = XRP(500).value(); + auto const reqBal = XRP(500); env(claim(alice, chan, reqBal, reqBal)); } } @@ -741,15 +729,14 @@ struct PayChan_test : public beast::unit_test::suite env.close(); // alice claims. Fails because bob's lsfDepositAuth flag is set. - env(claim(alice, chan, XRP(500).value(), XRP(500).value()), - ter(tecNO_PERMISSION)); + env(claim(alice, chan, XRP(500), XRP(500)), ter(tecNO_PERMISSION)); env.close(); // Claim with signature auto const baseFee = env.current()->fees().base; auto const preBob = env.balance(bob); { - auto const delta = XRP(500).value(); + auto const delta = XRP(500); auto const sig = signClaimAuth(pk, alice.sk(), chan, delta); // alice claims with signature. Fails since bob has @@ -773,7 +760,7 @@ struct PayChan_test : public beast::unit_test::suite } { // Explore the limits of deposit preauthorization. - auto const delta = XRP(600).value(); + auto const delta = XRP(600); auto const sig = signClaimAuth(pk, alice.sk(), chan, delta); // carol claims and fails. Only channel participants (bob or @@ -810,7 +797,7 @@ struct PayChan_test : public beast::unit_test::suite { // bob removes preauthorization of alice. Once again she // cannot submit a claim. - auto const delta = XRP(800).value(); + auto const delta = XRP(800); env(deposit::unauth(bob, alice)); env.close(); @@ -1543,13 +1530,7 @@ struct PayChan_test : public beast::unit_test::suite auto const authAmt = XRP(100); auto const sig = signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); - jv = claim( - bob, - chan, - authAmt.value(), - authAmt.value(), - Slice(sig), - alice.pk()); + jv = claim(bob, chan, authAmt, authAmt, Slice(sig), alice.pk()); jv["PublicKey"] = pkHex.substr(2, pkHex.size() - 2); env(jv, ter(temMALFORMED)); jv["PublicKey"] = pkHex.substr(0, pkHex.size() - 2); diff --git a/src/test/app/SetAuth_test.cpp b/src/test/app/SetAuth_test.cpp index 3dd8ab590a4..adb909314d3 100644 --- a/src/test/app/SetAuth_test.cpp +++ b/src/test/app/SetAuth_test.cpp @@ -38,8 +38,8 @@ struct SetAuth_test : public beast::unit_test::suite using namespace jtx; Json::Value jv; jv[jss::Account] = account.human(); - jv[jss::LimitAmount] = STAmount(Issue{to_currency(currency), dest}) - .getJson(JsonOptions::none); + jv[jss::LimitAmount] = + STAmount({to_currency(currency), dest}).getJson(JsonOptions::none); jv[jss::TransactionType] = jss::TrustSet; jv[jss::Flags] = tfSetfAuth; return jv; diff --git a/src/test/app/TheoreticalQuality_test.cpp b/src/test/app/TheoreticalQuality_test.cpp index 917d23377bf..95e2dd0d9ab 100644 --- a/src/test/app/TheoreticalQuality_test.cpp +++ b/src/test/app/TheoreticalQuality_test.cpp @@ -48,7 +48,7 @@ struct RippleCalcTestParams explicit RippleCalcTestParams(Json::Value const& jv) : srcAccount{*parseBase58(jv[jss::Account].asString())} , dstAccount{*parseBase58(jv[jss::Destination].asString())} - , dstAmt{amountFromJson(sfAmount, jv[jss::Amount])} + , dstAmt{get(amountFromJson(sfAmount, jv[jss::Amount]))} { if (jv.isMember(jss::SendMax)) sendMax = amountFromJson(sfSendMax, jv[jss::SendMax]); diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 4f24d17601e..21a5ed44c30 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -122,13 +122,13 @@ struct SEnv STAmount balance(jtx::Account const& account) const { - return env_.balance(account).value(); + return env_.balance(account); } STAmount balance(jtx::Account const& account, Issue const& issue) const { - return env_.balance(account, issue).value(); + return env_.balance(account, issue); } XRPAmount diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index eebc18f3e8b..fb5cef102ef 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -73,8 +73,7 @@ constexpr XRPAmount dropsPerXRP{1'000'000}; struct PrettyAmount { private: - // VFALCO TODO should be Amount - STAmount amount_; + STEitherAmount amount_; std::string name_; public: @@ -87,6 +86,10 @@ struct PrettyAmount : amount_(amount), name_(name) { } + PrettyAmount(STMPTAmount const& amount, std::string const& name) + : amount_(amount), name_(name) + { + } /** drops */ template @@ -95,7 +98,7 @@ struct PrettyAmount std::enable_if_t< sizeof(T) >= sizeof(int) && std::is_integral_v && std::is_signed_v>* = nullptr) - : amount_((v > 0) ? v : -v, v < 0) + : amount_(STAmount((v > 0) ? v : -v, v < 0)) { } @@ -105,7 +108,7 @@ struct PrettyAmount T v, std::enable_if_t= sizeof(int) && std::is_unsigned_v>* = nullptr) - : amount_(v) + : amount_(STAmount(v)) { } @@ -120,13 +123,18 @@ struct PrettyAmount return name_; } - STAmount const& + STEitherAmount const& value() const { return amount_; } operator STAmount const &() const + { + return get(amount_); + } + + operator STEitherAmount const &() const { return amount_; } @@ -134,6 +142,13 @@ struct PrettyAmount operator AnyAmount() const; }; +template +A const& +get(PrettyAmount const& pa) +{ + return get(pa.value()); +} + inline bool operator==(PrettyAmount const& lhs, PrettyAmount const& rhs) { @@ -314,10 +329,6 @@ class IOU { return issue(); } - operator Asset() const - { - return issue(); - } template < class T, @@ -355,69 +366,45 @@ operator<<(std::ostream& os, IOU const& iou); //------------------------------------------------------------------------------ -/** Converts to MPT Issue or STAmount. +/** Converts to MPT Issue or STMPTAmount. Examples: MPT Converts to the underlying Issue - MPT(10) Returns STAmount of 10 of + MPT(10) Returns STMPTAmount of 10 of the underlying MPT */ class MPT { public: std::string name; - ripple::MPT mptID; + ripple::MPTID mptID; - MPT(std::string const& n, ripple::MPT const& mptID_) + MPT(std::string const& n, ripple::MPTID const& mptID_) : name(n), mptID(mptID_) { } - ripple::MPT const& + ripple::MPTID const& mpt() const { return mptID; } - /** Implicit conversion to Issue. + /** Implicit conversion to MPTIssue. This allows passing an MPT value where an Issue is expected. */ - operator ripple::MPT() const + operator ripple::MPTID() const { return mpt(); } template - requires(sizeof(T) >= sizeof(int) && std::is_arithmetic_v) PrettyAmount + requires(sizeof(T) >= sizeof(int) && std::is_arithmetic_v) STMPTAmount operator()(T v) const { - // VFALCO NOTE Should throw if the - // representation of v is not exact. - return {amountFromString(mpt(), std::to_string(v)), name}; - } - - PrettyAmount operator()(epsilon_t) const; - PrettyAmount operator()(detail::epsilon_multiple) const; - - // VFALCO TODO - // STAmount operator()(char const* s) const; - - /** Returns None-of-Issue */ -#if 0 - None operator()(none_t) const - { - return {Issue{}}; - } -#endif - - friend BookSpec - operator~(MPT const& mpt) - { - assert(false); - Throw("MPT is not supported"); - return BookSpec{beast::zero, noCurrency()}; + return amountFromString(mptID, std::to_string(v)); } }; @@ -436,13 +423,22 @@ struct any_t struct AnyAmount { bool is_any; - STAmount value; + STEitherAmount value; AnyAmount() = delete; AnyAmount(AnyAmount const&) = default; AnyAmount& operator=(AnyAmount const&) = default; + AnyAmount(STEitherAmount const& amount) : is_any(false), value(amount) + { + } + + AnyAmount(STEitherAmount const& amount, any_t const*) + : is_any(true), value(amount) + { + } + AnyAmount(STAmount const& amount) : is_any(false), value(amount) { } @@ -452,13 +448,25 @@ struct AnyAmount { } + AnyAmount(STMPTAmount const& amount) : is_any(false), value(amount) + { + } + + AnyAmount(STMPTAmount const& amount, any_t const*) + : is_any(true), value(amount) + { + } + // Reset the issue to a specific account void to(AccountID const& id) { if (!is_any) return; - value.setIssuer(id); + if (value.isIssue()) + get(value).setIssuer(id); + else + Throw("AnyAmount: Amount is not STAmount"); } }; diff --git a/src/test/jtx/attester.h b/src/test/jtx/attester.h index 327fb2e4873..8bb5d57196a 100644 --- a/src/test/jtx/attester.h +++ b/src/test/jtx/attester.h @@ -42,7 +42,7 @@ sign_claim_attestation( SecretKey const& sk, STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, + STEitherAmount const& sendingAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t claimID, @@ -54,8 +54,8 @@ sign_create_account_attestation( SecretKey const& sk, STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, - STAmount const& rewardAmount, + STEitherAmount const& sendingAmount, + STEitherAmount const& rewardAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, diff --git a/src/test/jtx/delivermin.h b/src/test/jtx/delivermin.h index 46e633dab20..95ca676c292 100644 --- a/src/test/jtx/delivermin.h +++ b/src/test/jtx/delivermin.h @@ -31,12 +31,15 @@ namespace jtx { class delivermin { private: - STAmount amount_; + STEitherAmount amount_; public: delivermin(STAmount const& amount) : amount_(amount) { } + delivermin(STMPTAmount const& amount) : amount_(amount) + { + } void operator()(Env&, JTx& jtx) const; diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 575e2e1d889..b7e4af927dd 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -249,7 +249,8 @@ AMMTest::find_paths( STAmount da; if (result.isMember(jss::destination_amount)) - da = amountFromJson(sfGeneric, result[jss::destination_amount]); + da = get( + amountFromJson(sfGeneric, result[jss::destination_amount])); STAmount sa; STPathSet paths; @@ -261,10 +262,12 @@ AMMTest::find_paths( auto const& path = alts[0u]; if (path.isMember(jss::source_amount)) - sa = amountFromJson(sfGeneric, path[jss::source_amount]); + sa = get( + amountFromJson(sfGeneric, path[jss::source_amount])); if (path.isMember(jss::destination_amount)) - da = amountFromJson(sfGeneric, path[jss::destination_amount]); + da = get( + amountFromJson(sfGeneric, path[jss::destination_amount])); if (path.isMember(jss::paths_computed)) { diff --git a/src/test/jtx/impl/amount.cpp b/src/test/jtx/impl/amount.cpp index 01fa5369592..752d68ef1fd 100644 --- a/src/test/jtx/impl/amount.cpp +++ b/src/test/jtx/impl/amount.cpp @@ -72,29 +72,40 @@ to_places(const T d, std::uint8_t places) std::ostream& operator<<(std::ostream& os, PrettyAmount const& amount) { - if (amount.value().native()) + if (amount.value().isIssue()) { - // measure in hundredths - auto const c = dropsPerXRP.drops() / 100; - auto const n = amount.value().mantissa(); - if (n < c) + auto const& stAmount = get(amount); + if (stAmount.native()) { - if (amount.value().negative()) - os << "-" << n << " drops"; - else - os << n << " drops"; - return os; + // measure in hundredths + auto const c = dropsPerXRP.drops() / 100; + auto const n = stAmount.mantissa(); + if (n < c) + { + if (stAmount.negative()) + os << "-" << n << " drops"; + else + os << n << " drops"; + return os; + } + auto const d = double(n) / dropsPerXRP.drops(); + if (stAmount.negative()) + os << "-"; + + os << to_places(d, 6) << " XRP"; + } + else + { + os << stAmount.getText() << "/" + << to_string(stAmount.issue().currency) << "(" << amount.name() + << ")"; } - auto const d = double(n) / dropsPerXRP.drops(); - if (amount.value().negative()) - os << "-"; - - os << to_places(d, 6) << " XRP"; } else { - os << amount.value().getText() << "/" - << to_string(amount.value().issue().currency) << "(" << amount.name() + auto const& mptAmount = get(amount); + os << mptAmount.getText() << "/" + << to_string(mptAmount.issue().getMptID()) << "(" << amount.name() << ")"; } return os; diff --git a/src/test/jtx/impl/attester.cpp b/src/test/jtx/impl/attester.cpp index 66be9da83b3..02a61ffe9bd 100644 --- a/src/test/jtx/impl/attester.cpp +++ b/src/test/jtx/impl/attester.cpp @@ -35,7 +35,7 @@ sign_claim_attestation( SecretKey const& sk, STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, + STEitherAmount const& sendingAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t claimID, @@ -58,8 +58,8 @@ sign_create_account_attestation( SecretKey const& sk, STXChainBridge const& bridge, AccountID const& sendingAccount, - STAmount const& sendingAmount, - STAmount const& rewardAmount, + STEitherAmount const& sendingAmount, + STEitherAmount const& rewardAmount, AccountID const& rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index c9788a6d70f..94e02132c35 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -45,8 +45,8 @@ setupConfigForUnitTests(Config& cfg) // Default fees to old values, so tests don't have to worry about changes in // Config.h cfg.FEES.reference_fee = 10; - cfg.FEES.account_reserve = XRP(200).value().xrp().drops(); - cfg.FEES.owner_reserve = XRP(50).value().xrp().drops(); + cfg.FEES.account_reserve = get(XRP(200)).xrp().drops(); + cfg.FEES.owner_reserve = get(XRP(50)).xrp().drops(); // The Beta API (currently v2) is always available to tests cfg.BETA_RPC_API = true; diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 4602eb88305..88f206796bf 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -94,8 +94,7 @@ MPTTester::create(const MPTCreate& arg) { if (issuanceKey_) Throw("MPT can't be reused"); - mpt_ = std::make_pair(env_.seq(issuer_), issuer_.id()); - id_ = getMptID(issuer_.id(), mpt_->first); + id_ = getMptID(issuer_.id(), env_.seq(issuer_)); issuanceKey_ = keylet::mptIssuance(*id_).key; Json::Value jv; jv[sfAccount.jsonName] = issuer_.human(); @@ -119,7 +118,6 @@ MPTTester::create(const MPTCreate& arg) id_.reset(); issuanceKey_.reset(); - mpt_.reset(); } else if (arg.flags) env_.require(mptflags(*this, *arg.flags)); @@ -278,7 +276,7 @@ MPTTester::forObject( [[nodiscard]] bool MPTTester::checkMPTokenAmount( Account const& holder_, - std::uint64_t expectedAmount) const + std::int64_t expectedAmount) const { return forObject( [&](SLEP const& sle) { return expectedAmount == (*sle)[sfMPTAmount]; }, @@ -286,7 +284,7 @@ MPTTester::checkMPTokenAmount( } [[nodiscard]] bool -MPTTester::checkMPTokenOutstandingAmount(std::uint64_t expectedAmount) const +MPTTester::checkMPTokenOutstandingAmount(std::int64_t expectedAmount) const { return forObject([&](SLEP const& sle) { return expectedAmount == (*sle)[sfOutstandingAmount]; @@ -303,10 +301,10 @@ void MPTTester::pay( Account const& src, Account const& dest, - std::uint64_t amount, + std::int64_t amount, std::optional err) { - assert(mpt_); + assert(id_); auto const srcAmt = getAmount(src); auto const destAmt = getAmount(dest); auto const outstnAmt = getAmount(issuer_); @@ -330,15 +328,13 @@ MPTTester::pay( } else { - STAmount const saAmount = {*mpt_, amount}; - STAmount const saActual = - multiply(saAmount, transferRateMPT(*env_.current(), *mpt_)); + auto const actual = static_cast( + amount * Number{transferRate(*env_.current(), *id_).value, -9}); // Sender pays the transfer fee if any - env_.require(mptpay(*this, src, srcAmt - saActual.mpt().mpt())); + env_.require(mptpay(*this, src, srcAmt - actual)); env_.require(mptpay(*this, dest, destAmt + amount)); // Outstanding amount is reduced by the transfer fee if any - env_.require(mptpay( - *this, issuer_, outstnAmt - (saActual - saAmount).mpt().mpt())); + env_.require(mptpay(*this, issuer_, outstnAmt - (actual - amount))); } } @@ -346,10 +342,10 @@ void MPTTester::claw( Account const& issuer, Account const& holder, - std::uint64_t amount, + std::int64_t amount, std::optional err) { - assert(mpt_); + assert(id_); auto const issuerAmt = getAmount(issuer); auto const holderAmt = getAmount(holder); if (err) @@ -367,14 +363,14 @@ MPTTester::claw( mptpay(*this, holder, holderAmt - std::min(holderAmt, amount))); } -PrettyAmount -MPTTester::mpt(std::uint64_t amount) const +STMPTAmount +MPTTester::mpt(std::int64_t amount) const { - assert(mpt_); - return ripple::test::jtx::MPT(issuer_.name(), *mpt_)(amount); + assert(id_); + return ripple::test::jtx::MPT(issuer_.name(), *id_)(amount); } -std::uint64_t +std::int64_t MPTTester::getAmount(Account const& account) const { assert(issuanceKey_); @@ -396,12 +392,13 @@ std::uint32_t MPTTester::getFlags(ripple::test::jtx::AccountP holder) const { std::uint32_t flags = 0; - assert(forObject( - [&](SLEP const& sle) { - flags = sle->getFlags(); - return true; - }, - holder)); + if (!forObject( + [&](SLEP const& sle) { + flags = sle->getFlags(); + return true; + }, + holder)) + Throw("Failed to get the flags"); return flags; } diff --git a/src/test/jtx/impl/offer.cpp b/src/test/jtx/impl/offer.cpp index 55a1af4beab..d6e2d89bc21 100644 --- a/src/test/jtx/impl/offer.cpp +++ b/src/test/jtx/impl/offer.cpp @@ -27,8 +27,8 @@ namespace jtx { Json::Value offer( Account const& account, - STAmount const& takerPays, - STAmount const& takerGets, + STEitherAmount const& takerPays, + STEitherAmount const& takerGets, std::uint32_t flags) { Json::Value jv; diff --git a/src/test/jtx/impl/paths.cpp b/src/test/jtx/impl/paths.cpp index 393e36e9d61..8c58f81a627 100644 --- a/src/test/jtx/impl/paths.cpp +++ b/src/test/jtx/impl/paths.cpp @@ -31,7 +31,8 @@ paths::operator()(Env& env, JTx& jt) const auto& jv = jt.jv; auto const from = env.lookup(jv[jss::Account].asString()); auto const to = env.lookup(jv[jss::Destination].asString()); - auto const amount = amountFromJson(sfAmount, jv[jss::Amount]); + auto const amount = + get(amountFromJson(sfAmount, jv[jss::Amount])); Pathfinder pf( std::make_shared( env.current(), env.app().journal("RippleLineCache")), diff --git a/src/test/jtx/impl/trust.cpp b/src/test/jtx/impl/trust.cpp index 320c7d05c7c..94f9d49ed46 100644 --- a/src/test/jtx/impl/trust.cpp +++ b/src/test/jtx/impl/trust.cpp @@ -66,7 +66,7 @@ trust( Json::Value claw( Account const& account, - STAmount const& amount, + STEitherAmount const& amount, std::optional const& mptHolder) { Json::Value jv; diff --git a/src/test/jtx/impl/xchain_bridge.cpp b/src/test/jtx/impl/xchain_bridge.cpp index 43b0e7c2f96..32d24c0027a 100644 --- a/src/test/jtx/impl/xchain_bridge.cpp +++ b/src/test/jtx/impl/xchain_bridge.cpp @@ -115,7 +115,7 @@ Json::Value xchain_create_claim_id( Account const& acc, Json::Value const& bridge, - STAmount const& reward, + STEitherAmount const& reward, Account const& otherChainSource) { Json::Value jv; diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 9353cbf27b7..e96b73f039a 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -60,10 +60,10 @@ class mptpay private: MPTTester const& tester_; Account const& account_; - std::uint64_t const amount_; + std::int64_t const amount_; public: - mptpay(MPTTester& tester, Account const& account, std::uint64_t amount) + mptpay(MPTTester& tester, Account const& account, std::int64_t amount) : tester_(tester), account_(account), amount_(amount) { } @@ -97,7 +97,7 @@ struct MPTConstr struct MPTCreate { - std::optional maxAmt = std::nullopt; + std::optional maxAmt = std::nullopt; std::optional assetScale = std::nullopt; std::optional transferFee = std::nullopt; std::optional metadata = std::nullopt; @@ -111,7 +111,7 @@ struct MPTCreate struct MPTDestroy { AccountP issuer = nullptr; - std::optional id = std::nullopt; + std::optional id = std::nullopt; std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; @@ -122,7 +122,7 @@ struct MPTAuthorize { AccountP account = nullptr; AccountP holder = nullptr; - std::optional id = std::nullopt; + std::optional id = std::nullopt; std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; @@ -133,7 +133,7 @@ struct MPTSet { AccountP account = nullptr; AccountP holder = nullptr; - std::optional id = std::nullopt; + std::optional id = std::nullopt; std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; @@ -145,9 +145,8 @@ class MPTTester Env& env_; Account const& issuer_; std::unordered_map const holders_; - std::optional id_; + std::optional id_; std::optional issuanceKey_; - std::optional mpt_; bool close_; public: @@ -166,11 +165,11 @@ class MPTTester set(MPTSet const& set = {}); [[nodiscard]] bool - checkMPTokenAmount(Account const& holder, std::uint64_t expectedAmount) + checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount) const; [[nodiscard]] bool - checkMPTokenOutstandingAmount(std::uint64_t expectedAmount) const; + checkMPTokenOutstandingAmount(std::int64_t expectedAmount) const; [[nodiscard]] bool checkFlags(uint32_t const expectedFlags, AccountP holder = nullptr) const; @@ -186,18 +185,18 @@ class MPTTester void pay(Account const& src, Account const& dest, - std::uint64_t amount, + std::int64_t amount, std::optional err = std::nullopt); void claw( Account const& issuer, Account const& holder, - std::uint64_t amount, + std::int64_t amount, std::optional err = std::nullopt); - PrettyAmount - mpt(std::uint64_t amount) const; + STMPTAmount + mpt(std::int64_t amount) const; uint256 const& issuanceKey() const @@ -206,14 +205,14 @@ class MPTTester return *issuanceKey_; } - uint192 const& + MPTID const& issuanceID() const { assert(id_); return *id_; } - std::uint64_t + std::int64_t getAmount(Account const& account) const; private: diff --git a/src/test/jtx/offer.h b/src/test/jtx/offer.h index 3951f4f934a..2d66b24436e 100644 --- a/src/test/jtx/offer.h +++ b/src/test/jtx/offer.h @@ -32,8 +32,8 @@ namespace jtx { Json::Value offer( Account const& account, - STAmount const& takerPays, - STAmount const& takerGets, + STEitherAmount const& takerPays, + STEitherAmount const& takerGets, std::uint32_t flags = 0); /** Cancel an offer. */ diff --git a/src/test/jtx/sendmax.h b/src/test/jtx/sendmax.h index 495c61c33b8..86f23dc6894 100644 --- a/src/test/jtx/sendmax.h +++ b/src/test/jtx/sendmax.h @@ -31,12 +31,15 @@ namespace jtx { class sendmax { private: - STAmount amount_; + STEitherAmount amount_; public: sendmax(STAmount const& amount) : amount_(amount) { } + sendmax(STMPTAmount const& amount) : amount_(amount) + { + } void operator()(Env&, JTx& jtx) const; diff --git a/src/test/jtx/trust.h b/src/test/jtx/trust.h index 0d02c6e76c4..03e045591ce 100644 --- a/src/test/jtx/trust.h +++ b/src/test/jtx/trust.h @@ -43,7 +43,7 @@ trust( Json::Value claw( Account const& account, - STAmount const& amount, + STEitherAmount const& amount, std::optional const& mptHolder = std::nullopt); } // namespace jtx diff --git a/src/test/jtx/xchain_bridge.h b/src/test/jtx/xchain_bridge.h index 9968317c8de..dd219d0dfcd 100644 --- a/src/test/jtx/xchain_bridge.h +++ b/src/test/jtx/xchain_bridge.h @@ -61,7 +61,7 @@ Json::Value xchain_create_claim_id( Account const& acc, Json::Value const& bridge, - STAmount const& reward, + STEitherAmount const& reward, Account const& otherChainSource); Json::Value diff --git a/src/test/protocol/Quality_test.cpp b/src/test/protocol/Quality_test.cpp index 64cf0c71b3a..741a341d980 100644 --- a/src/test/protocol/Quality_test.cpp +++ b/src/test/protocol/Quality_test.cpp @@ -29,7 +29,7 @@ class Quality_test : public beast::unit_test::suite // Create a raw, non-integral amount from mantissa and exponent STAmount static raw(std::uint64_t mantissa, int exponent) { - return STAmount(Issue{Currency(3), AccountID(3)}, mantissa, exponent); + return STAmount({Currency(3), AccountID(3)}, mantissa, exponent); } template diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index e48d0500ba6..532a0b984a3 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -34,7 +34,7 @@ class STAmount_test : public beast::unit_test::suite s.add(ser); SerialIter sit(ser.slice()); - return STAmount(sit, sfGeneric); + return STAmount(sit); } //-------------------------------------------------------------------------- diff --git a/src/test/protocol/STObject_test.cpp b/src/test/protocol/STObject_test.cpp index 39a5b9c2f65..b55c1959363 100644 --- a/src/test/protocol/STObject_test.cpp +++ b/src/test/protocol/STObject_test.cpp @@ -560,10 +560,10 @@ class STObject_test : public beast::unit_test::suite { STObject st(sfGeneric); - st[sfAmount] = STAmount{}; + st[sfAmount] = STEitherAmount{}; st[sfAccount] = AccountID{}; st[sfDigest] = uint256{}; - [&](STAmount) {}(st[sfAmount]); + [&](STEitherAmount) {}(st[sfAmount]); [&](AccountID) {}(st[sfAccount]); [&](uint256) {}(st[sfDigest]); } diff --git a/src/test/protocol/STTx_test.cpp b/src/test/protocol/STTx_test.cpp index e0f6796af33..934bd8050c0 100644 --- a/src/test/protocol/STTx_test.cpp +++ b/src/test/protocol/STTx_test.cpp @@ -1344,22 +1344,22 @@ class STTx_test : public beast::unit_test::suite 0x73, 0x00, 0x81, 0x14, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xfe, 0xf3, 0xe7, 0xe5, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x1e, 0x00, 0x6f, 0x00, 0x00, 0x20, 0x1f, 0x03, 0xf6, 0x00, - 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x59, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x12, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x1e, 0x00, 0x4f, 0x00, 0x00, 0x20, 0x1f, 0x03, 0xf6, 0x00, - 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x35, 0x24, 0x59, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x54, 0x72, 0x61, 0x6e, 0x00, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xe5, 0xfe, 0xf3, 0xe7, 0xe5, 0x65, 0x24, 0x00, - 0x00, 0x00, 0x00, 0x20, 0x1e, 0x00, 0x6f, 0x00, 0x00, 0x20, 0xf6, - 0x00, 0x00, 0x03, 0x1f, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x35, - 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x73, 0x00, 0x81, 0x14, 0x00, - 0x10, 0x00, 0x73, 0x00, 0x81, 0x14, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xfe}; + 0x20, 0x1e, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x20, 0x1f, 0x03, 0xf6, + 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x59, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x12, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x1e, 0x00, 0x4f, 0x00, 0x00, 0x20, 0x1f, 0x03, 0xf6, + 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x35, 0x24, 0x59, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x54, 0x72, 0x61, 0x6e, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xe5, 0xfe, 0xf3, 0xe7, 0xe5, 0x65, 0x24, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x1e, 0x00, 0x6f, 0x00, 0x00, 0x20, + 0xf6, 0x00, 0x00, 0x03, 0x1f, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x73, 0x00, 0x81, 0x14, + 0x00, 0x10, 0x00, 0x73, 0x00, 0x81, 0x14, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0xe5, 0xfe}; // Construct an STObject with 11 levels of object nesting so the // maximum nesting level exception is thrown. diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index e5475e3f530..b84f7f191f3 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -388,7 +388,7 @@ class AccountSet_test : public beast::unit_test::suite auto const amount = USD(1); Rate const rate(transferRate * QUALITY_ONE); auto const amountWithRate = - toAmount(multiply(amount.value(), rate)); + toAmount(multiply(amount, rate)); env(pay(gw, alice, USD(10))); env.close(); @@ -455,7 +455,7 @@ class AccountSet_test : public beast::unit_test::suite auto const amount = USD(1); auto const amountWithRate = toAmount( - multiply(amount.value(), Rate(transferRate * QUALITY_ONE))); + multiply(amount, Rate(transferRate * QUALITY_ONE))); env(pay(gw, alice, USD(10))); env(pay(alice, bob, amount), sendmax(USD(10))); diff --git a/src/xrpld/app/paths/Credit.cpp b/src/xrpld/app/paths/Credit.cpp index b3870937367..c11f628a11d 100644 --- a/src/xrpld/app/paths/Credit.cpp +++ b/src/xrpld/app/paths/Credit.cpp @@ -31,7 +31,7 @@ creditLimit( AccountID const& issuer, Currency const& currency) { - STAmount result(Issue{currency, account}); + STAmount result({currency, account}); auto sleRippleState = view.read(keylet::line(account, issuer, currency)); @@ -64,7 +64,7 @@ creditBalance( AccountID const& issuer, Currency const& currency) { - STAmount result(Issue{currency, account}); + STAmount result({currency, account}); auto sleRippleState = view.read(keylet::line(account, issuer, currency)); diff --git a/src/xrpld/app/paths/PathRequest.cpp b/src/xrpld/app/paths/PathRequest.cpp index bb6a104bca2..4cd9f7d71f7 100644 --- a/src/xrpld/app/paths/PathRequest.cpp +++ b/src/xrpld/app/paths/PathRequest.cpp @@ -562,7 +562,7 @@ PathRequest::findPaths( }(); STAmount saMaxAmount = saSendMax.value_or( - STAmount(Issue{issue.currency, sourceAccount}, 1u, 0, true)); + STAmount({issue.currency, sourceAccount}, 1u, 0, true)); JLOG(m_journal.debug()) << iIdentifier << " Paths found, calling rippleCalc"; diff --git a/src/xrpld/app/paths/Pathfinder.cpp b/src/xrpld/app/paths/Pathfinder.cpp index a5fe2afe949..885a8ae9b47 100644 --- a/src/xrpld/app/paths/Pathfinder.cpp +++ b/src/xrpld/app/paths/Pathfinder.cpp @@ -176,10 +176,9 @@ Pathfinder::Pathfinder( , mSrcCurrency(uSrcCurrency) , mSrcIssuer(uSrcIssuer) , mSrcAmount(srcAmount.value_or(STAmount( - Issue{ - uSrcCurrency, - uSrcIssuer.value_or( - isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)}, + {uSrcCurrency, + uSrcIssuer.value_or( + isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)}, 1u, 0, true))) diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp index 237e1afa240..3ed35d72656 100644 --- a/src/xrpld/app/tx/detail/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -47,7 +47,7 @@ AMMCreate::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } - auto const amount = ctx.tx[sfAmount]; + auto const amount = get(ctx.tx[sfAmount]); auto const amount2 = ctx.tx[sfAmount2]; if (amount.issue() == amount2.issue()) @@ -89,7 +89,7 @@ TER AMMCreate::preclaim(PreclaimContext const& ctx) { auto const accountID = ctx.tx[sfAccount]; - auto const amount = ctx.tx[sfAmount]; + auto const amount = get(ctx.tx[sfAmount]); auto const amount2 = ctx.tx[sfAmount2]; // Check if AMM already exists for the token pair @@ -208,7 +208,7 @@ applyCreate( AccountID const& account_, beast::Journal j_) { - auto const amount = ctx_.tx[sfAmount]; + auto const amount = get(ctx_.tx[sfAmount]); auto const amount2 = ctx_.tx[sfAmount2]; auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue()); diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 9bbf5b4a60a..75993ad4189 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -48,7 +48,7 @@ AMMDeposit::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } - auto const amount = ctx.tx[~sfAmount]; + auto const amount = get(ctx.tx[~sfAmount]); auto const amount2 = ctx.tx[~sfAmount2]; auto const ePrice = ctx.tx[~sfEPrice]; auto const lpTokens = ctx.tx[~sfLPTokenOut]; @@ -244,7 +244,7 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) : tecUNFUNDED_AMM; }; - auto const amount = ctx.tx[~sfAmount]; + auto const amount = get(ctx.tx[~sfAmount]); auto const amount2 = ctx.tx[~sfAmount2]; auto const ammAccountID = ammSle->getAccountID(sfAccount); @@ -339,7 +339,7 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) std::pair AMMDeposit::applyGuts(Sandbox& sb) { - auto const amount = ctx_.tx[~sfAmount]; + auto const amount = get(ctx_.tx[~sfAmount]); auto const amount2 = ctx_.tx[~sfAmount2]; auto const ePrice = ctx_.tx[~sfEPrice]; auto const lpTokensDeposit = ctx_.tx[~sfLPTokenOut]; diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 51b512aba0a..394fd79dd8c 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -48,7 +48,7 @@ AMMWithdraw::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } - auto const amount = ctx.tx[~sfAmount]; + auto const amount = get(ctx.tx[~sfAmount]); auto const amount2 = ctx.tx[~sfAmount2]; auto const ePrice = ctx.tx[~sfEPrice]; auto const lpTokens = ctx.tx[~sfLPTokenIn]; @@ -181,7 +181,7 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) return terNO_AMM; } - auto const amount = ctx.tx[~sfAmount]; + auto const amount = get(ctx.tx[~sfAmount]); auto const amount2 = ctx.tx[~sfAmount2]; auto const expected = ammHolds( @@ -295,7 +295,7 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) std::pair AMMWithdraw::applyGuts(Sandbox& sb) { - auto const amount = ctx_.tx[~sfAmount]; + auto const amount = get(ctx_.tx[~sfAmount]); auto const amount2 = ctx_.tx[~sfAmount2]; auto const ePrice = ctx_.tx[~sfEPrice]; auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2])); diff --git a/src/xrpld/app/tx/detail/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp index 8b5ef79b6d4..585bf9dcc99 100644 --- a/src/xrpld/app/tx/detail/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -50,7 +50,7 @@ CashCheck::preflight(PreflightContext const& ctx) } // Exactly one of Amount or DeliverMin must be present. - auto const optAmount = ctx.tx[~sfAmount]; + auto const optAmount = get(ctx.tx[~sfAmount]); auto const optDeliverMin = ctx.tx[~sfDeliverMin]; if (static_cast(optAmount) == static_cast(optDeliverMin)) @@ -136,7 +136,7 @@ CashCheck::preclaim(PreclaimContext const& ctx) // Preflight verified exactly one of Amount or DeliverMin is present. // Make sure the requested amount is reasonable. STAmount const value{[](STTx const& tx) { - auto const optAmount = tx[~sfAmount]; + auto const optAmount = get(tx[~sfAmount]); return optAmount ? *optAmount : tx[sfDeliverMin]; }(ctx.tx)}; @@ -307,7 +307,7 @@ CashCheck::doApply() STAmount const xrpDeliver{ optDeliverMin ? std::max(*optDeliverMin, std::min(sendMax, srcLiquid)) - : ctx_.tx.getFieldAmount(sfAmount)}; + : get(ctx_.tx.getFieldAmount(sfAmount))}; if (srcLiquid < xrpDeliver) { @@ -340,11 +340,12 @@ CashCheck::doApply() // transfer rate to account for. Since the transfer rate cannot // exceed 200%, we use 1/2 maxValue as our limit. STAmount const flowDeliver{ - optDeliverMin ? STAmount( - optDeliverMin->issue(), - STAmount::cMaxValue / 2, - STAmount::cMaxOffset) - : ctx_.tx.getFieldAmount(sfAmount)}; + optDeliverMin + ? STAmount( + optDeliverMin->issue(), + STAmount::cMaxValue / 2, + STAmount::cMaxOffset) + : get(ctx_.tx.getFieldAmount(sfAmount))}; // If a trust line does not exist yet create one. Issue const& trustLineIssue = flowDeliver.issue(); diff --git a/src/xrpld/app/tx/detail/Clawback.cpp b/src/xrpld/app/tx/detail/Clawback.cpp index 08c0098f3b2..a6ca307bb3d 100644 --- a/src/xrpld/app/tx/detail/Clawback.cpp +++ b/src/xrpld/app/tx/detail/Clawback.cpp @@ -28,61 +28,81 @@ namespace ripple { +template +static NotTEC +preflightHelper(PreflightContext const& ctx); + +template <> NotTEC -Clawback::preflight(PreflightContext const& ctx) +preflightHelper(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureClawback)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (ctx.tx.getFlags() & tfClawbackMask) + return temINVALID_FLAG; + + if (ctx.tx.isFieldPresent(sfMPTokenHolder)) + return temMALFORMED; + + AccountID const issuer = ctx.tx[sfAccount]; + STAmount const clawAmount = get(ctx.tx[sfAmount]); + + // The issuer field is used for the token holder instead + AccountID const& holder = clawAmount.getIssuer(); + + if (issuer == holder || isXRP(clawAmount) || clawAmount <= beast::zero) + return temBAD_AMOUNT; + + return preflight2(ctx); +} + +template <> +NotTEC +preflightHelper(PreflightContext const& ctx) { if (!ctx.rules.enabled(featureClawback)) return temDISABLED; auto const mptHolder = ctx.tx[~sfMPTokenHolder]; - STAmount const clawAmount = ctx.tx[sfAmount]; - if ((mptHolder || clawAmount.isMPT()) && - !ctx.rules.enabled(featureMPTokensV1)) + auto const clawAmount = get(ctx.tx[sfAmount]); + + if (!ctx.rules.enabled(featureMPTokensV1)) return temDISABLED; if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - if (!mptHolder && clawAmount.isMPT()) - return temMALFORMED; - - if (mptHolder && !clawAmount.isMPT()) + if (!mptHolder) return temMALFORMED; if (ctx.tx.getFlags() & tfClawbackMask) return temINVALID_FLAG; - AccountID const issuer = ctx.tx[sfAccount]; + // issuer is the same as holder + if (ctx.tx[sfAccount] == *mptHolder) + return temMALFORMED; - // The issuer field is used for the token holder if asset is IOU - AccountID const& holder = - clawAmount.isMPT() ? *mptHolder : clawAmount.getIssuer(); - - if (clawAmount.isMPT()) - { - if (issuer == holder) - return temMALFORMED; - - if (clawAmount.mpt() > MPTAmount{maxMPTokenAmount} || - clawAmount <= beast::zero) - return temBAD_AMOUNT; - } - else - { - if (issuer == holder || isXRP(clawAmount) || clawAmount <= beast::zero) - return temBAD_AMOUNT; - } + if (clawAmount > MPTAmount{maxMPTokenAmount} || clawAmount <= beast::zero) + return temBAD_AMOUNT; return preflight2(ctx); } +template +static TER +preclaimHelper(PreclaimContext const& ctx); + +template <> TER -Clawback::preclaim(PreclaimContext const& ctx) +preclaimHelper(PreclaimContext const& ctx) { AccountID const issuer = ctx.tx[sfAccount]; - STAmount const clawAmount = ctx.tx[sfAmount]; - AccountID const& holder = - clawAmount.isMPT() ? ctx.tx[sfMPTokenHolder] : clawAmount.getIssuer(); + STAmount const clawAmount = get(ctx.tx[sfAmount]); + AccountID const& holder = clawAmount.getIssuer(); auto const sleIssuer = ctx.view.read(keylet::account(issuer)); auto const sleHolder = ctx.view.read(keylet::account(holder)); @@ -92,125 +112,174 @@ Clawback::preclaim(PreclaimContext const& ctx) if (sleHolder->isFieldPresent(sfAMMID)) return tecAMM_ACCOUNT; - if (clawAmount.isMPT()) - { - auto const issuanceKey = - keylet::mptIssuance(clawAmount.mptIssue().mpt()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - if (!((*sleIssuance)[sfFlags] & lsfMPTCanClawback)) - return tecNO_PERMISSION; - - if (sleIssuance->getAccountID(sfIssuer) != issuer) - return tecNO_PERMISSION; - - if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, holder))) - return tecOBJECT_NOT_FOUND; - - if (accountHolds( - ctx.view, - holder, - clawAmount.mptIssue(), - fhIGNORE_FREEZE, - ahIGNORE_AUTH, - ctx.j) <= beast::zero) - return tecINSUFFICIENT_FUNDS; - } - else - { - std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags); - - // If AllowTrustLineClawback is not set or NoFreeze is set, return no - // permission - if (!(issuerFlagsIn & lsfAllowTrustLineClawback) || - (issuerFlagsIn & lsfNoFreeze)) - return tecNO_PERMISSION; - - auto const sleRippleState = ctx.view.read( - keylet::line(holder, issuer, clawAmount.getCurrency())); - if (!sleRippleState) - return tecNO_LINE; - - STAmount const& balance = (*sleRippleState)[sfBalance]; - - // If balance is positive, issuer must have higher address than holder - if (balance > beast::zero && issuer < holder) - return tecNO_PERMISSION; - - // If balance is negative, issuer must have lower address than holder - if (balance < beast::zero && issuer > holder) - return tecNO_PERMISSION; - - // At this point, we know that issuer and holder accounts - // are correct and a trustline exists between them. - // - // Must now explicitly check the balance to make sure - // available balance is non-zero. - // - // We can't directly check the balance of trustline because - // the available balance of a trustline is prone to new changes (eg. - // XLS-34). So we must use `accountHolds`. - if (accountHolds( - ctx.view, - holder, - clawAmount.getCurrency(), - issuer, - fhIGNORE_FREEZE, - ctx.j) <= beast::zero) - return tecINSUFFICIENT_FUNDS; - } + std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags); + + // If AllowTrustLineClawback is not set or NoFreeze is set, return no + // permission + if (!(issuerFlagsIn & lsfAllowTrustLineClawback) || + (issuerFlagsIn & lsfNoFreeze)) + return tecNO_PERMISSION; + + auto const sleRippleState = + ctx.view.read(keylet::line(holder, issuer, clawAmount.getCurrency())); + if (!sleRippleState) + return tecNO_LINE; + + STAmount const balance = (*sleRippleState)[sfBalance]; + + // If balance is positive, issuer must have higher address than holder + if (balance > beast::zero && issuer < holder) + return tecNO_PERMISSION; + + // If balance is negative, issuer must have lower address than holder + if (balance < beast::zero && issuer > holder) + return tecNO_PERMISSION; + + // At this point, we know that issuer and holder accounts + // are correct and a trustline exists between them. + // + // Must now explicitly check the balance to make sure + // available balance is non-zero. + // + // We can't directly check the balance of trustline because + // the available balance of a trustline is prone to new changes (eg. + // XLS-34). So we must use `accountHolds`. + if (accountHolds( + ctx.view, + holder, + clawAmount.getCurrency(), + issuer, + fhIGNORE_FREEZE, + ctx.j) <= beast::zero) + return tecINSUFFICIENT_FUNDS; return tesSUCCESS; } +template <> TER -Clawback::doApply() +preclaimHelper(PreclaimContext const& ctx) { - AccountID const& issuer = account_; - STAmount clawAmount = ctx_.tx[sfAmount]; - AccountID const holder = clawAmount.isMPT() - ? ctx_.tx[sfMPTokenHolder] - : clawAmount.getIssuer(); // cannot be reference because clawAmount is - // modified beblow - - if (clawAmount.isMPT()) - { - // Get the spendable balance. Must use `accountHolds`. - STAmount const spendableAmount = accountHolds( - view(), + AccountID const issuer = ctx.tx[sfAccount]; + auto const clawAmount = get(ctx.tx[sfAmount]); + AccountID const& holder = ctx.tx[sfMPTokenHolder]; + + auto const sleIssuer = ctx.view.read(keylet::account(issuer)); + auto const sleHolder = ctx.view.read(keylet::account(holder)); + if (!sleIssuer || !sleHolder) + return terNO_ACCOUNT; + + if (sleHolder->isFieldPresent(sfAMMID)) + return tecAMM_ACCOUNT; + + auto const issuanceKey = keylet::mptIssuance(clawAmount.issue().getMptID()); + auto const sleIssuance = ctx.view.read(issuanceKey); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + if (!((*sleIssuance)[sfFlags] & lsfMPTCanClawback)) + return tecNO_PERMISSION; + + if (sleIssuance->getAccountID(sfIssuer) != issuer) + return tecNO_PERMISSION; + + if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, holder))) + return tecOBJECT_NOT_FOUND; + + if (accountHolds( + ctx.view, holder, - clawAmount.mptIssue(), + clawAmount.issue(), fhIGNORE_FREEZE, ahIGNORE_AUTH, - j_); + ctx.j) <= beast::zero) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; +} + +template +static TER +applyHelper(ApplyContext& ctx); - return rippleMPTCredit( - view(), holder, issuer, std::min(spendableAmount, clawAmount), j_); - } +template <> +TER +applyHelper(ApplyContext& ctx) +{ + AccountID const& issuer = ctx.tx[sfAccount]; + STAmount clawAmount = get(ctx.tx[sfAmount]); + AccountID const holder = clawAmount.getIssuer(); // cannot be reference - // Replace the `issuer` field with issuer's account if asset is IOU + // Replace the `issuer` field with issuer's account clawAmount.setIssuer(issuer); if (holder == issuer) return tecINTERNAL; // Get the spendable balance. Must use `accountHolds`. STAmount const spendableAmount = accountHolds( - view(), + ctx.view(), holder, clawAmount.getCurrency(), clawAmount.getIssuer(), fhIGNORE_FREEZE, - j_); + ctx.journal); return rippleCredit( - view(), + ctx.view(), holder, issuer, std::min(spendableAmount, clawAmount), true, - j_); + ctx.journal); +} + +template <> +TER +applyHelper(ApplyContext& ctx) +{ + AccountID const& issuer = ctx.tx[sfAccount]; + auto clawAmount = get(ctx.tx[sfAmount]); + AccountID const holder = ctx.tx[sfMPTokenHolder]; + + // Get the spendable balance. Must use `accountHolds`. + STMPTAmount const spendableAmount = accountHolds( + ctx.view(), + holder, + clawAmount.issue(), + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + ctx.journal); + + return rippleCredit( + ctx.view(), + holder, + issuer, + std::min(spendableAmount, clawAmount), + ctx.journal); +} + +NotTEC +Clawback::preflight(PreflightContext const& ctx) +{ + return std::visit( + [&](T const&) { return preflightHelper(ctx); }, + ctx.tx[sfAmount].getValue()); +} + +TER +Clawback::preclaim(PreclaimContext const& ctx) +{ + return std::visit( + [&](T const&) { return preclaimHelper(ctx); }, + ctx.tx[sfAmount].getValue()); +} + +TER +Clawback::doApply() +{ + return std::visit( + [&](T const&) { return applyHelper(ctx_); }, + ctx_.tx[sfAmount].getValue()); } } // namespace ripple diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index e34b675998d..bcf187262c5 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -93,7 +93,7 @@ after(NetClock::time_point now, std::uint32_t mark) TxConsequences EscrowCreate::makeTxConsequences(PreflightContext const& ctx) { - return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; + return TxConsequences{ctx.tx, get(ctx.tx[sfAmount]).xrp()}; } NotTEC @@ -108,7 +108,7 @@ EscrowCreate::preflight(PreflightContext const& ctx) if (!isXRP(ctx.tx[sfAmount])) return temBAD_AMOUNT; - if (ctx.tx[sfAmount] <= beast::zero) + if (get(ctx.tx[sfAmount]) <= beast::zero) return temBAD_AMOUNT; // We must specify at least one timeout value @@ -219,7 +219,7 @@ EscrowCreate::doApply() if (balance < reserve) return tecINSUFFICIENT_RESERVE; - if (balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp()) + if (balance < reserve + get(ctx_.tx[sfAmount]).xrp()) return tecUNFUNDED; } @@ -276,7 +276,7 @@ EscrowCreate::doApply() } // Deduct owner's balance, increment owner count - (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; + (*sle)[sfBalance] = (*sle)[sfBalance] - get(ctx_.tx[sfAmount]); adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); ctx_.view().update(sle); @@ -496,7 +496,7 @@ EscrowFinish::doApply() } // Transfer amount to destination - (*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount]; + (*sled)[sfBalance] = (*sled)[sfBalance] + get((*slep)[sfAmount]); ctx_.view().update(sled); // Adjust source owner count @@ -582,7 +582,7 @@ EscrowCancel::doApply() // Transfer amount back to owner, decrement owner count auto const sle = ctx_.view().peek(keylet::account(account)); - (*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount]; + (*sle)[sfBalance] = (*sle)[sfBalance] + get((*slep)[sfAmount]); adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); ctx_.view().update(sle); diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 625f8c7b284..718efb33017 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -102,10 +102,12 @@ XRPNotCreated::visitEntry( break; case ltPAYCHAN: drops_ -= - ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops(); + (get((*before)[sfAmount]) - (*before)[sfBalance]) + .xrp() + .drops(); break; case ltESCROW: - drops_ -= (*before)[sfAmount].xrp().drops(); + drops_ -= get((*before)[sfAmount]).xrp().drops(); break; default: break; @@ -121,13 +123,14 @@ XRPNotCreated::visitEntry( break; case ltPAYCHAN: if (!isDelete) - drops_ += ((*after)[sfAmount] - (*after)[sfBalance]) + drops_ += (get((*after)[sfAmount]) - + (*after)[sfBalance]) .xrp() .drops(); break; case ltESCROW: if (!isDelete) - drops_ += (*after)[sfAmount].xrp().drops(); + drops_ += get((*after)[sfAmount]).xrp().drops(); break; default: break; @@ -279,10 +282,10 @@ NoZeroEscrow::visitEntry( }; if (before && before->getType() == ltESCROW) - bad_ |= isBad((*before)[sfAmount]); + bad_ |= isBad(get((*before)[sfAmount])); if (after && after->getType() == ltESCROW) - bad_ |= isBad((*after)[sfAmount]); + bad_ |= isBad(get((*after)[sfAmount])); } bool @@ -919,7 +922,7 @@ ValidClawback::finalize( if (trustlinesChanged == 1) { AccountID const issuer = tx.getAccountID(sfAccount); - STAmount const& amount = tx.getFieldAmount(sfAmount); + STAmount const& amount = get(tx.getFieldAmount(sfAmount)); AccountID const& holder = amount.getIssuer(); STAmount const holderBalance = accountHolds( view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j); diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index b884a791e78..258a96a412d 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -104,7 +104,8 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) return tecNFTOKEN_BUY_SELL_MISMATCH; // The two offers being brokered must be for the same asset: - if ((*bo)[sfAmount].issue() != (*so)[sfAmount].issue()) + if (get((*bo)[sfAmount]).issue() != + get((*so)[sfAmount]).issue()) return tecNFTOKEN_BUY_SELL_MISMATCH; // The two offers may not form a loop. A broker may not sell the @@ -115,7 +116,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) // Ensure that the buyer is willing to pay at least as much as the // seller is requesting: - if ((*so)[sfAmount] > (*bo)[sfAmount]) + if (get((*so)[sfAmount]) > get((*bo)[sfAmount])) return tecINSUFFICIENT_PAYMENT; // If the buyer specified a destination @@ -152,13 +153,14 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) // cut, if any). if (auto const brokerFee = ctx.tx[~sfNFTokenBrokerFee]) { - if (brokerFee->issue() != (*bo)[sfAmount].issue()) + if (brokerFee->issue() != get((*bo)[sfAmount]).issue()) return tecNFTOKEN_BUY_SELL_MISMATCH; - if (brokerFee >= (*bo)[sfAmount]) + if (brokerFee >= get((*bo)[sfAmount])) return tecINSUFFICIENT_PAYMENT; - if ((*so)[sfAmount] > (*bo)[sfAmount] - *brokerFee) + if (get((*so)[sfAmount]) > + get((*bo)[sfAmount]) - *brokerFee) return tecINSUFFICIENT_PAYMENT; } } @@ -191,7 +193,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) // // After this amendment, we allow an IOU issuer to buy an NFT with their // own currency - auto const needed = bo->at(sfAmount); + auto const needed = get(bo->at(sfAmount)); if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2)) { if (accountFunds( @@ -234,7 +236,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) } // The account offering to buy must have funds: - auto const needed = so->at(sfAmount); + auto const needed = get(so->at(sfAmount)); if (!ctx.view.rules().enabled(fixNonFungibleTokensV1_2)) { if (accountHolds( @@ -280,7 +282,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) return tecINTERNAL; uint256 const& tokenID = offer->at(sfNFTokenID); - STAmount const& amount = offer->at(sfAmount); + STAmount const& amount = get(offer->at(sfAmount)); if (nft::getTransferFee(tokenID) != 0 && (nft::getFlags(tokenID) & nft::flagCreateTrustLines) == 0 && !amount.native()) @@ -387,7 +389,8 @@ NFTokenAcceptOffer::acceptOffer(std::shared_ptr const& offer) auto const nftokenID = (*offer)[sfNFTokenID]; - if (auto amount = offer->getFieldAmount(sfAmount); amount != beast::zero) + if (auto amount = get(offer->getFieldAmount(sfAmount)); + amount != beast::zero) { // Calculate the issuer's cut from this sale, if any: if (auto const fee = nft::getTransferFee(nftokenID); fee != 0) @@ -448,7 +451,7 @@ NFTokenAcceptOffer::doApply() auto const nftokenID = (*so)[sfNFTokenID]; // The amount is what the buyer of the NFT pays: - STAmount amount = (*bo)[sfAmount]; + STAmount amount = get((*bo)[sfAmount]); // Three different folks may be paid. The order of operations is // important. diff --git a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp index 43178d31b4a..c132df1ffda 100644 --- a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp @@ -46,7 +46,7 @@ NFTokenCreateOffer::preflight(PreflightContext const& ctx) // Use implementation shared with NFTokenMint if (NotTEC notTec = nft::tokenOfferCreatePreflight( ctx.tx[sfAccount], - ctx.tx[sfAmount], + get(ctx.tx[sfAmount]), ctx.tx[~sfDestination], ctx.tx[~sfExpiration], nftFlags, @@ -79,7 +79,7 @@ NFTokenCreateOffer::preclaim(PreclaimContext const& ctx) ctx.view, ctx.tx[sfAccount], nft::getIssuer(nftokenID), - ctx.tx[sfAmount], + get(ctx.tx[sfAmount]), ctx.tx[~sfDestination], nft::getFlags(nftokenID), nft::getTransferFee(nftokenID), @@ -95,7 +95,7 @@ NFTokenCreateOffer::doApply() return nft::tokenOfferCreateApply( view(), ctx_.tx[sfAccount], - ctx_.tx[sfAmount], + get(ctx_.tx[sfAmount]), ctx_.tx[~sfDestination], ctx_.tx[~sfExpiration], ctx_.tx.getSeqProxy(), diff --git a/src/xrpld/app/tx/detail/NFTokenMint.cpp b/src/xrpld/app/tx/detail/NFTokenMint.cpp index d5c3a8707c2..06ff1932f3f 100644 --- a/src/xrpld/app/tx/detail/NFTokenMint.cpp +++ b/src/xrpld/app/tx/detail/NFTokenMint.cpp @@ -105,7 +105,7 @@ NFTokenMint::preflight(PreflightContext const& ctx) // because a Mint is only allowed to create a sell offer. if (NotTEC notTec = nft::tokenOfferCreatePreflight( ctx.tx[sfAccount], - ctx.tx[sfAmount], + get(ctx.tx[sfAmount]), ctx.tx[~sfDestination], ctx.tx[~sfExpiration], extractNFTokenFlagsFromTxFlags(ctx.tx.getFlags()), @@ -195,7 +195,7 @@ NFTokenMint::preclaim(PreclaimContext const& ctx) ctx.view, ctx.tx[sfAccount], ctx.tx[~sfIssuer].value_or(ctx.tx[sfAccount]), - ctx.tx[sfAmount], + get(ctx.tx[sfAmount]), ctx.tx[~sfDestination], extractNFTokenFlagsFromTxFlags(ctx.tx.getFlags()), ctx.tx[~sfTransferFee].value_or(0), @@ -323,7 +323,7 @@ NFTokenMint::doApply() if (TER const ter = nft::tokenOfferCreateApply( view(), ctx_.tx[sfAccount], - ctx_.tx[sfAmount], + get(ctx_.tx[sfAmount]), ctx_.tx[~sfDestination], ctx_.tx[~sfExpiration], ctx_.tx.getSeqProxy(), diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.cpp b/src/xrpld/app/tx/detail/NFTokenUtils.cpp index 61ff8e200b3..c4e65543014 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -1020,7 +1020,7 @@ tokenOfferCreateApply( auto offer = std::make_shared(offerID); (*offer)[sfOwner] = acctID; (*offer)[sfNFTokenID] = nftokenID; - (*offer)[sfAmount] = amount; + (*offer)[sfAmount] = STEitherAmount{amount}; (*offer)[sfFlags] = sleFlags; (*offer)[sfOwnerNode] = *ownerNode; (*offer)[sfNFTokenOfferNode] = *offerNode; diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index d17736c4738..e3a2e02e02d 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -149,9 +149,9 @@ closeChannel( if (!sle) return tefINTERNAL; - assert((*slep)[sfAmount] >= (*slep)[sfBalance]); - (*sle)[sfBalance] = - (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance]; + assert(get((*slep)[sfAmount]) >= (*slep)[sfBalance]); + (*sle)[sfBalance] = (*sle)[sfBalance] + get((*slep)[sfAmount]) - + (*slep)[sfBalance]; adjustOwnerCount(view, sle, -1, j); view.update(sle); @@ -165,7 +165,7 @@ closeChannel( TxConsequences PayChanCreate::makeTxConsequences(PreflightContext const& ctx) { - return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; + return TxConsequences{ctx.tx, get(ctx.tx[sfAmount]).xrp()}; } NotTEC @@ -177,7 +177,8 @@ PayChanCreate::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero)) + if (!isXRP(ctx.tx[sfAmount]) || + (get(ctx.tx[sfAmount]) <= beast::zero)) return temBAD_AMOUNT; if (ctx.tx[sfAccount] == ctx.tx[sfDestination]) @@ -206,7 +207,7 @@ PayChanCreate::preclaim(PreclaimContext const& ctx) if (balance < reserve) return tecINSUFFICIENT_RESERVE; - if (balance < reserve + ctx.tx[sfAmount]) + if (balance < reserve + get(ctx.tx[sfAmount])) return tecUNFUNDED; } @@ -262,7 +263,7 @@ PayChanCreate::doApply() // Funds held in this channel (*slep)[sfAmount] = ctx_.tx[sfAmount]; // Amount channel has already paid - (*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed(); + (*slep)[sfBalance] = get(ctx_.tx[sfAmount]).zeroed(); (*slep)[sfAccount] = account; (*slep)[sfDestination] = dst; (*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay]; @@ -295,7 +296,7 @@ PayChanCreate::doApply() } // Deduct owner's balance, increment owner count - (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; + (*sle)[sfBalance] = (*sle)[sfBalance] - get(ctx_.tx[sfAmount]); adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); ctx_.view().update(sle); @@ -307,7 +308,7 @@ PayChanCreate::doApply() TxConsequences PayChanFund::makeTxConsequences(PreflightContext const& ctx) { - return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; + return TxConsequences{ctx.tx, get(ctx.tx[sfAmount]).xrp()}; } NotTEC @@ -319,7 +320,8 @@ PayChanFund::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero)) + if (!isXRP(ctx.tx[sfAmount]) || + (get(ctx.tx[sfAmount]) <= beast::zero)) return temBAD_AMOUNT; return preflight2(ctx); @@ -378,7 +380,7 @@ PayChanFund::doApply() if (balance < reserve) return tecINSUFFICIENT_RESERVE; - if (balance < reserve + ctx_.tx[sfAmount]) + if (balance < reserve + get(ctx_.tx[sfAmount])) return tecUNFUNDED; } @@ -389,10 +391,11 @@ PayChanFund::doApply() return tecNO_DST; } - (*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount]; + (*slep)[sfAmount] = STEitherAmount{ + get((*slep)[sfAmount]) + get(ctx_.tx[sfAmount])}; ctx_.view().update(slep); - (*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount]; + (*sle)[sfBalance] = (*sle)[sfBalance] - get(ctx_.tx[sfAmount]); ctx_.view().update(sle); return tesSUCCESS; @@ -410,7 +413,7 @@ PayChanClaim::preflight(PreflightContext const& ctx) if (bal && (!isXRP(*bal) || *bal <= beast::zero)) return temBAD_AMOUNT; - auto const amt = ctx.tx[~sfAmount]; + auto const amt = get(ctx.tx[~sfAmount]); if (amt && (!isXRP(*amt) || *amt <= beast::zero)) return temBAD_AMOUNT; @@ -485,7 +488,8 @@ PayChanClaim::doApply() if (ctx_.tx[~sfBalance]) { auto const chanBalance = slep->getFieldAmount(sfBalance).xrp(); - auto const chanFunds = slep->getFieldAmount(sfAmount).xrp(); + auto const chanFunds = + get(slep->getFieldAmount(sfAmount)).xrp(); auto const reqBalance = ctx_.tx[sfBalance].xrp(); if (txAccount == dst && !ctx_.tx[~sfSignature]) @@ -549,7 +553,8 @@ PayChanClaim::doApply() if (ctx_.tx.getFlags() & tfClose) { // Channel will close immediately if dry or the receiver closes - if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount]) + if (dst == txAccount || + (*slep)[sfBalance] == get((*slep)[sfAmount])) return closeChannel( slep, ctx_.view(), k.key, ctx_.app.journal("View")); diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index e6ab5612519..42275ba4da6 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -29,12 +29,18 @@ namespace ripple { +template +static TxConsequences +makeTxConsequencesHelper(PreflightContext const& ctx); + +template <> TxConsequences -Payment::makeTxConsequences(PreflightContext const& ctx) +makeTxConsequencesHelper(PreflightContext const& ctx) { auto calculateMaxXRPSpend = [](STTx const& tx) -> XRPAmount { - STAmount const maxAmount = - tx.isFieldPresent(sfSendMax) ? tx[sfSendMax] : tx[sfAmount]; + auto const maxAmount = tx.isFieldPresent(sfSendMax) + ? tx[sfSendMax] + : get(tx[sfAmount]); // If there's no sfSendMax in XRP, and the sfAmount isn't // in XRP, then the transaction does not spend XRP. @@ -44,8 +50,20 @@ Payment::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, calculateMaxXRPSpend(ctx.tx)}; } +template <> +TxConsequences +makeTxConsequencesHelper(PreflightContext const& ctx) +{ + return TxConsequences{ctx.tx, beast::zero}; +} + +template +static NotTEC +preflightHelper(PreflightContext const& ctx); + +template <> NotTEC -Payment::preflight(PreflightContext const& ctx) +preflightHelper(PreflightContext const& ctx) { if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -68,7 +86,7 @@ Payment::preflight(PreflightContext const& ctx) bool const bPaths = tx.isFieldPresent(sfPaths); bool const bMax = tx.isFieldPresent(sfSendMax); - STAmount const saDstAmount(tx.getFieldAmount(sfAmount)); + STAmount const saDstAmount(get(tx.getFieldAmount(sfAmount))); STAmount maxSourceAmount; auto const account = tx.getAccountID(sfAccount); @@ -78,26 +96,17 @@ Payment::preflight(PreflightContext const& ctx) else if (saDstAmount.native()) maxSourceAmount = saDstAmount; else - { - auto const asset = [&]() -> Asset { - if (saDstAmount.isMPT()) - return saDstAmount.asset(); - return Issue{saDstAmount.getCurrency(), account}; - }(); maxSourceAmount = STAmount( - asset, + {saDstAmount.getCurrency(), account}, saDstAmount.mantissa(), saDstAmount.exponent(), saDstAmount < beast::zero); - } - auto const& uSrcAsset = maxSourceAmount.asset(); - auto const& uDstAsset = saDstAmount.asset(); + auto const& uSrcCurrency = maxSourceAmount.getCurrency(); + auto const& uDstCurrency = saDstAmount.getCurrency(); // isZero() is XRP. FIX! - bool const bXRPDirect = isXRP(uSrcAsset) && isXRP(uDstAsset); - bool const bMPTDirect = isMPT(uSrcAsset) && isMPT(uDstAsset); - bool const bDirect = bXRPDirect || bMPTDirect; + bool const bXRPDirect = uSrcCurrency.isZero() && uDstCurrency.isZero(); if (!isLegalNet(saDstAmount) || !isLegalNet(maxSourceAmount)) return temBAD_AMOUNT; @@ -122,58 +131,55 @@ Payment::preflight(PreflightContext const& ctx) << "bad dst amount: " << saDstAmount.getFullText(); return temBAD_AMOUNT; } - if ((uSrcAsset.isIssue() && badCurrency() == uSrcAsset.issue().currency) || - (uDstAsset.isIssue() && badCurrency() == uDstAsset.issue().currency)) + if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency) { JLOG(j.trace()) << "Malformed transaction: " << "Bad currency."; return temBAD_CURRENCY; } - if (account == uDstAccountID && uSrcAsset == uDstAsset && !bPaths) + if (account == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) { // You're signing yourself a payment. // If bPaths is true, you might be trying some arbitrage. JLOG(j.trace()) << "Malformed transaction: " << "Redundant payment from " << to_string(account) - << " to self without path for " << to_string(uDstAsset); + << " to self without path for " + << to_string(uDstCurrency); return temREDUNDANT; } - if (bDirect && bMax) + if (bXRPDirect && bMax) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " - << "SendMax specified for XRP to XRP or MPT to MPT."; - return temBAD_SEND_XRP_MAX; // TODO MPT new err code here and below + << "SendMax specified for XRP to XRP."; + return temBAD_SEND_XRP_MAX; } - if (bDirect && bPaths) + if (bXRPDirect && bPaths) { // XRP is sent without paths. JLOG(j.trace()) << "Malformed transaction: " - << "Paths specified for XRP to XRP or MPT to MPT."; + << "Paths specified for XRP to XRP."; return temBAD_SEND_XRP_PATHS; } - if (bDirect && partialPaymentAllowed) + if (bXRPDirect && partialPaymentAllowed) { // Consistent but redundant transaction. - JLOG(j.trace()) - << "Malformed transaction: " - << "Partial payment specified for XRP to XRP or MPT to MPT."; + JLOG(j.trace()) << "Malformed transaction: " + << "Partial payment specified for XRP to XRP."; return temBAD_SEND_XRP_PARTIAL; } - if (bDirect && limitQuality) + if (bXRPDirect && limitQuality) { // Consistent but redundant transaction. - JLOG(j.trace()) - << "Malformed transaction: " - << "Limit quality specified for XRP to XRP or MPT to MPT."; + JLOG(j.trace()) << "Malformed transaction: " + << "Limit quality specified for XRP to XRP."; return temBAD_SEND_XRP_LIMIT; } - if (bDirect && !defaultPathsAllowed) + if (bXRPDirect && !defaultPathsAllowed) { // Consistent but redundant transaction. - JLOG(j.trace()) - << "Malformed transaction: " - << "No ripple direct specified for XRP to XRP or MPT to MPT."; + JLOG(j.trace()) << "Malformed transaction: " + << "No ripple direct specified for XRP to XRP."; return temBAD_SEND_XRP_NO_DIRECT; } @@ -216,8 +222,91 @@ Payment::preflight(PreflightContext const& ctx) return preflight2(ctx); } +template <> +NotTEC +preflightHelper(PreflightContext const& ctx) +{ + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + if (!ctx.rules.enabled(featureMPTokensV1)) + return temDISABLED; + + if (ctx.tx.isFieldPresent(sfDeliverMin) || + ctx.tx.isFieldPresent(sfSendMax) || ctx.tx.isFieldPresent(sfPaths)) + return temMALFORMED; + + auto& tx = ctx.tx; + auto& j = ctx.j; + + std::uint32_t const uTxFlags = tx.getFlags(); + + if (uTxFlags & tfPaymentMask) + { + JLOG(j.trace()) << "Malformed transaction: " + << "Invalid flags set."; + return temINVALID_FLAG; + } + + STMPTAmount const saDstAmount( + get(tx.getFieldAmount(sfAmount))); + + auto const account = tx.getAccountID(sfAccount); + + auto const& uDstCurrency = saDstAmount.getCurrency(); + + auto const uDstAccountID = tx.getAccountID(sfDestination); + + if (!uDstAccountID) + { + JLOG(j.trace()) << "Malformed transaction: " + << "Payment destination account not specified."; + return temDST_NEEDED; + } + if (saDstAmount <= beast::zero) + { + JLOG(j.trace()) << "Malformed transaction: " + << "bad dst amount: " << saDstAmount.getFullText(); + return temBAD_AMOUNT; + } + if (noMPT() == uDstCurrency) + { + JLOG(j.trace()) << "Malformed transaction: " + << "Bad asset."; + return temBAD_CURRENCY; + } + if (account == uDstAccountID) + { + // You're signing yourself a payment. + JLOG(j.trace()) << "Malformed transaction: " + << "Redundant payment from " << to_string(account) + << " to self without path for " + << to_string(uDstCurrency); + return temREDUNDANT; + } + if (uTxFlags & (tfPartialPayment | tfLimitQuality | tfNoRippleDirect)) + { + JLOG(j.trace()) << "Malformed transaction: invalid MPT flags: " + << uTxFlags; + return temMALFORMED; + } + + return preflight2(ctx); +} + +template +static TER +preclaimHelper( + PreclaimContext const& ctx, + std::size_t maxPathSize, + std::size_t maxPathLength); + +template <> TER -Payment::preclaim(PreclaimContext const& ctx) +preclaimHelper( + PreclaimContext const& ctx, + std::size_t maxPathSize, + std::size_t maxPathLength) { // Ripple if source or destination is non-native or if there are paths. std::uint32_t const uTxFlags = ctx.tx.getFlags(); @@ -226,7 +315,7 @@ Payment::preclaim(PreclaimContext const& ctx) auto const sendMax = ctx.tx[~sfSendMax]; AccountID const uDstAccountID(ctx.tx[sfDestination]); - STAmount const saDstAmount(ctx.tx[sfAmount]); + STAmount const saDstAmount(get(ctx.tx[sfAmount])); auto const k = keylet::account(uDstAccountID); auto const sleDst = ctx.view.read(k); @@ -288,9 +377,9 @@ Payment::preclaim(PreclaimContext const& ctx) { STPathSet const& paths = ctx.tx.getFieldPathSet(sfPaths); - if (paths.size() > MaxPathSize || - std::any_of(paths.begin(), paths.end(), [](STPath const& path) { - return path.size() > MaxPathLength; + if (paths.size() > maxPathSize || + std::any_of(paths.begin(), paths.end(), [&](STPath const& path) { + return path.size() > maxPathLength; })) { return telBAD_PATH_COUNT; @@ -300,76 +389,122 @@ Payment::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } +template <> TER -Payment::doApply() +preclaimHelper( + PreclaimContext const& ctx, + std::size_t, + std::size_t) { - auto const deliverMin = ctx_.tx[~sfDeliverMin]; + AccountID const uDstAccountID(ctx.tx[sfDestination]); + + auto const k = keylet::account(uDstAccountID); + auto const sleDst = ctx.view.read(k); + + if (!sleDst) + { + JLOG(ctx.j.trace()) + << "Delay transaction: Destination account does not exist."; + + // Another transaction could create the account and then this + // transaction would succeed. + return tecNO_DST; + } + else if ( + (sleDst->getFlags() & lsfRequireDestTag) && + !ctx.tx.isFieldPresent(sfDestinationTag)) + { + // The tag is basically account-specific information we don't + // understand, but we can require someone to fill it in. + + // We didn't make this test for a newly-formed account because there's + // no way for this field to be set. + JLOG(ctx.j.trace()) + << "Malformed transaction: DestinationTag required."; + + return tecDST_TAG_NEEDED; + } + + return tesSUCCESS; +} + +template +static TER +applyHelper( + ApplyContext& ctx, + XRPAmount const& priorBalance, + XRPAmount const& sourceBalance); + +template <> +TER +applyHelper( + ApplyContext& ctx, + XRPAmount const& priorBalance, + XRPAmount const& sourceBalance) +{ + AccountID const account = ctx.tx[sfAccount]; + auto const deliverMin = ctx.tx[~sfDeliverMin]; // Ripple if source or destination is non-native or if there are paths. - std::uint32_t const uTxFlags = ctx_.tx.getFlags(); + std::uint32_t const uTxFlags = ctx.tx.getFlags(); bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; bool const limitQuality = uTxFlags & tfLimitQuality; bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect); - auto const paths = ctx_.tx.isFieldPresent(sfPaths); - auto const sendMax = ctx_.tx[~sfSendMax]; + auto const paths = ctx.tx.isFieldPresent(sfPaths); + auto const sendMax = ctx.tx[~sfSendMax]; - AccountID const uDstAccountID(ctx_.tx.getAccountID(sfDestination)); - STAmount const saDstAmount(ctx_.tx.getFieldAmount(sfAmount)); + AccountID const uDstAccountID(ctx.tx.getAccountID(sfDestination)); + STAmount const saDstAmount(get(ctx.tx.getFieldAmount(sfAmount))); STAmount maxSourceAmount; if (sendMax) maxSourceAmount = *sendMax; else if (saDstAmount.native()) maxSourceAmount = saDstAmount; else - { - auto const asset = [&]() -> Asset { - if (saDstAmount.isMPT()) - return saDstAmount.asset(); - return Issue{saDstAmount.getCurrency(), account_}; - }(); maxSourceAmount = STAmount( - asset, + {saDstAmount.getCurrency(), account}, saDstAmount.mantissa(), saDstAmount.exponent(), saDstAmount < beast::zero); - } - JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText() - << " saDstAmount=" << saDstAmount.getFullText(); + JLOG(ctx.journal.trace()) + << "maxSourceAmount=" << maxSourceAmount.getFullText() + << " saDstAmount=" << saDstAmount.getFullText(); // Open a ledger for editing. auto const k = keylet::account(uDstAccountID); - SLE::pointer sleDst = view().peek(k); + SLE::pointer sleDst = ctx.view().peek(k); if (!sleDst) { std::uint32_t const seqno{ - view().rules().enabled(featureDeletableAccounts) ? view().seq() - : 1}; + ctx.view().rules().enabled(featureDeletableAccounts) + ? ctx.view().seq() + : 1}; // Create the account. sleDst = std::make_shared(k); sleDst->setAccountID(sfAccount, uDstAccountID); sleDst->setFieldU32(sfSequence, seqno); - view().insert(sleDst); + ctx.view().insert(sleDst); } else { // Tell the engine that we are intending to change the destination // account. The source account gets always charged a fee so it's always // marked as modified. - view().update(sleDst); + ctx.view().update(sleDst); } // Determine whether the destination requires deposit authorization. bool const reqDepositAuth = sleDst->getFlags() & lsfDepositAuth && - view().rules().enabled(featureDepositAuth); + ctx.view().rules().enabled(featureDepositAuth); - bool const depositPreauth = view().rules().enabled(featureDepositPreauth); + bool const depositPreauth = + ctx.view().rules().enabled(featureDepositPreauth); - bool const bRipple = - paths || sendMax || !(saDstAmount.native() || saDstAmount.isMPT()); + bool const bRipple = paths || sendMax || !saDstAmount.native(); // If the destination has lsfDepositAuth set, then only direct XRP // payments (no intermediate steps) are allowed to the destination. @@ -387,10 +522,10 @@ Payment::doApply() // authorization has two ways to get an IOU Payment in: // 1. If Account == Destination, or // 2. If Account is deposit preauthorized by destination. - if (uDstAccountID != account_) + if (uDstAccountID != account) { - if (!view().exists( - keylet::depositPreauth(uDstAccountID, account_))) + if (!ctx.view().exists( + keylet::depositPreauth(uDstAccountID, account))) return tecNO_PERMISSION; } } @@ -399,26 +534,26 @@ Payment::doApply() rcInput.partialPaymentAllowed = partialPaymentAllowed; rcInput.defaultPathsAllowed = defaultPathsAllowed; rcInput.limitQuality = limitQuality; - rcInput.isLedgerOpen = view().open(); + rcInput.isLedgerOpen = ctx.view().open(); path::RippleCalc::Output rc; { - PaymentSandbox pv(&view()); - JLOG(j_.debug()) << "Entering RippleCalc in payment: " - << ctx_.tx.getTransactionID(); + PaymentSandbox pv(&ctx.view()); + JLOG(ctx.journal.debug()) << "Entering RippleCalc in payment: " + << ctx.tx.getTransactionID(); rc = path::RippleCalc::rippleCalculate( pv, maxSourceAmount, saDstAmount, uDstAccountID, - account_, - ctx_.tx.getFieldPathSet(sfPaths), - ctx_.app.logs(), + account, + ctx.tx.getFieldPathSet(sfPaths), + ctx.app.logs(), &rcInput); // VFALCO NOTE We might not need to apply, depending // on the TER. But always applying *should* // be safe. - pv.apply(ctx_.rawView()); + pv.apply(ctx.rawView()); } // TODO: is this right? If the amount is the correct amount, was @@ -428,7 +563,7 @@ Payment::doApply() if (deliverMin && rc.actualAmountOut < *deliverMin) rc.setResult(tecPATH_PARTIAL); else - ctx_.deliver(rc.actualAmountOut); + ctx.deliver(rc.actualAmountOut); } auto terResult = rc.result(); @@ -441,46 +576,12 @@ Payment::doApply() terResult = tecPATH_DRY; return terResult; } - else if (saDstAmount.isMPT()) - { - if (auto const ter = - requireAuth(view(), saDstAmount.mptIssue(), account_); - ter != tesSUCCESS) - return ter; - - if (auto const ter = - requireAuth(view(), saDstAmount.mptIssue(), uDstAccountID); - ter != tesSUCCESS) - return ter; - - if (auto const ter = canTransfer( - view(), saDstAmount.mptIssue(), account_, uDstAccountID); - ter != tesSUCCESS) - return ter; - - auto const& mpt = saDstAmount.mptIssue(); - auto const& issuer = mpt.getIssuer(); - // If globally/individually locked then - // can't send between holders - // holder can send back to issuer - // issuer can send to holder - if (account_ != issuer && uDstAccountID != issuer && - (isFrozen(view(), account_, mpt) || - isFrozen(view(), uDstAccountID, mpt))) - return tecMPT_LOCKED; - - PaymentSandbox pv(&view()); - auto const res = - accountSendMPT(pv, account_, uDstAccountID, saDstAmount, j_); - pv.apply(ctx_.rawView()); - return res; - } assert(saDstAmount.native()); // Direct XRP payment. - auto const sleSrc = view().peek(keylet::account(account_)); + auto const sleSrc = ctx.view().peek(keylet::account(account)); if (!sleSrc) return tefINTERNAL; @@ -489,21 +590,21 @@ Payment::doApply() auto const uOwnerCount = sleSrc->getFieldU32(sfOwnerCount); // This is the total reserve in drops. - auto const reserve = view().fees().accountReserve(uOwnerCount); + auto const reserve = ctx.view().fees().accountReserve(uOwnerCount); // mPriorBalance is the balance on the sending account BEFORE the // fees were charged. We want to make sure we have enough reserve // to send. Allow final spend to use reserve for fee. - auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()); + auto const mmm = std::max(reserve, ctx.tx.getFieldAmount(sfFee).xrp()); - if (mPriorBalance < saDstAmount.xrp() + mmm) + if (priorBalance < saDstAmount.xrp() + mmm) { // Vote no. However the transaction might succeed, if applied in // a different order. - JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " - << " " << to_string(mPriorBalance) << " / " - << to_string(saDstAmount.xrp() + mmm) << " (" - << to_string(reserve) << ")"; + JLOG(ctx.journal.trace()) << "Delay transaction: Insufficient funds: " + << " " << to_string(priorBalance) << " / " + << to_string(saDstAmount.xrp() + mmm) << " (" + << to_string(reserve) << ")"; return tecUNFUNDED_PAYMENT; } @@ -535,12 +636,13 @@ Payment::doApply() // We choose the base reserve as our bound because it is // a small number that seldom changes but is always sufficient // to get the account un-wedged. - if (uDstAccountID != account_) + if (uDstAccountID != account) { - if (!view().exists(keylet::depositPreauth(uDstAccountID, account_))) + if (!ctx.view().exists( + keylet::depositPreauth(uDstAccountID, account))) { // Get the base reserve. - XRPAmount const dstReserve{view().fees().accountReserve(0)}; + XRPAmount const dstReserve{ctx.view().fees().accountReserve(0)}; if (saDstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve) @@ -550,7 +652,7 @@ Payment::doApply() } // Do the arithmetic for the transfer and make the ledger change. - sleSrc->setFieldAmount(sfBalance, mSourceBalance - saDstAmount); + sleSrc->setFieldAmount(sfBalance, sourceBalance - saDstAmount); sleDst->setFieldAmount( sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount); @@ -561,4 +663,85 @@ Payment::doApply() return tesSUCCESS; } +template <> +TER +applyHelper(ApplyContext& ctx, XRPAmount const&, XRPAmount const&) +{ + auto const account = ctx.tx[sfAccount]; + + AccountID const uDstAccountID(ctx.tx.getAccountID(sfDestination)); + auto const saDstAmount(get(ctx.tx.getFieldAmount(sfAmount))); + + JLOG(ctx.journal.trace()) << " saDstAmount=" << saDstAmount.getFullText(); + + if (auto const ter = requireAuth(ctx.view(), saDstAmount.issue(), account); + ter != tesSUCCESS) + return ter; + + if (auto const ter = + requireAuth(ctx.view(), saDstAmount.issue(), uDstAccountID); + ter != tesSUCCESS) + return ter; + + if (auto const ter = canTransfer( + ctx.view(), saDstAmount.issue(), account, uDstAccountID); + ter != tesSUCCESS) + return ter; + + auto const& mpt = saDstAmount.issue(); + auto const& issuer = mpt.getIssuer(); + // If globally/individually locked then + // - can't send between holders + // - holder can send back to issuer + // - issuer can send to holder + if (account != issuer && uDstAccountID != issuer && + (isFrozen(ctx.view(), account, mpt) || + isFrozen(ctx.view(), uDstAccountID, mpt))) + return tecMPT_LOCKED; + + PaymentSandbox pv(&ctx.view()); + auto const res = + accountSend(pv, account, uDstAccountID, saDstAmount, ctx.journal); + pv.apply(ctx.rawView()); + return res; +} + +TxConsequences +Payment::makeTxConsequences(PreflightContext const& ctx) +{ + return std::visit( + [&](TDel const&) { + return makeTxConsequencesHelper(ctx); + }, + ctx.tx[sfAmount].getValue()); +} + +NotTEC +Payment::preflight(PreflightContext const& ctx) +{ + return std::visit( + [&](TDel const&) { return preflightHelper(ctx); }, + ctx.tx[sfAmount].getValue()); +} + +TER +Payment::preclaim(PreclaimContext const& ctx) +{ + return std::visit( + [&](TDel const&) { + return preclaimHelper(ctx, MaxPathSize, MaxPathLength); + }, + ctx.tx[sfAmount].getValue()); +} + +TER +Payment::doApply() +{ + return std::visit( + [&](TDel const&) { + return applyHelper(ctx_, mPriorBalance, mSourceBalance); + }, + ctx_.tx[sfAmount].getValue()); +} + } // namespace ripple diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 6ae8be8a67f..a528943f4cf 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -843,7 +843,7 @@ Transactor::operator()() // fixSTAmountCanonicalize predate the rulesGuard and should be replaced. STAmountSO stAmountSO{view().rules().enabled(fixSTAmountCanonicalize)}; NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)}; - CurrentTransactionRulesGuard currentTransctionRulesGuard(view().rules()); + CurrentTransactionRulesGuard currentTransactionRulesGuard(view().rules()); #ifdef DEBUG { diff --git a/src/xrpld/app/tx/detail/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp index f5633903567..3154d605ca1 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -1676,7 +1676,7 @@ XChainClaim::preflight(PreflightContext const& ctx) return temINVALID_FLAG; STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; - auto const amount = ctx.tx[sfAmount]; + auto const amount = get(ctx.tx[sfAmount]); if (amount.signum() <= 0 || (amount.issue() != bridgeSpec.lockingChainIssue() && @@ -1693,7 +1693,7 @@ XChainClaim::preclaim(PreclaimContext const& ctx) { AccountID const account = ctx.tx[sfAccount]; STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; - STAmount const& thisChainAmount = ctx.tx[sfAmount]; + STAmount const& thisChainAmount = get(ctx.tx[sfAmount]); auto const claimID = ctx.tx[sfXChainClaimID]; auto const sleBridge = readBridge(ctx.view, bridgeSpec); @@ -1780,7 +1780,7 @@ XChainClaim::doApply() AccountID const account = ctx_.tx[sfAccount]; auto const dst = ctx_.tx[sfDestination]; STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge]; - STAmount const& thisChainAmount = ctx_.tx[sfAmount]; + STAmount const& thisChainAmount = get(ctx_.tx[sfAmount]); auto const claimID = ctx_.tx[sfXChainClaimID]; auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID); @@ -1892,7 +1892,7 @@ TxConsequences XChainCommit::makeTxConsequences(PreflightContext const& ctx) { auto const maxSpend = [&] { - auto const amount = ctx.tx[sfAmount]; + auto const amount = get(ctx.tx[sfAmount]); if (amount.native() && amount.signum() > 0) return amount.xrp(); return XRPAmount{beast::zero}; @@ -1913,7 +1913,7 @@ XChainCommit::preflight(PreflightContext const& ctx) if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; - auto const amount = ctx.tx[sfAmount]; + auto const amount = get(ctx.tx[sfAmount]); auto const bridgeSpec = ctx.tx[sfXChainBridge]; if (amount.signum() <= 0 || !isLegalNet(amount)) @@ -1959,12 +1959,14 @@ XChainCommit::preclaim(PreclaimContext const& ctx) if (isLockingChain) { - if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue()) + if (bridgeSpec.lockingChainIssue() != + get(ctx.tx[sfAmount]).issue()) return tecXCHAIN_BAD_TRANSFER_ISSUE; } else { - if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue()) + if (bridgeSpec.issuingChainIssue() != + get(ctx.tx[sfAmount]).issue()) return tecXCHAIN_BAD_TRANSFER_ISSUE; } @@ -1977,7 +1979,7 @@ XChainCommit::doApply() PaymentSandbox psb(&ctx_.view()); auto const account = ctx_.tx[sfAccount]; - auto const amount = ctx_.tx[sfAmount]; + auto const amount = get(ctx_.tx[sfAmount]); auto const bridgeSpec = ctx_.tx[sfXChainBridge]; if (!psb.read(keylet::account(account))) @@ -2182,7 +2184,7 @@ XChainCreateAccountCommit::preflight(PreflightContext const& ctx) if (ctx.tx.getFlags() & tfUniversalMask) return temINVALID_FLAG; - auto const amount = ctx.tx[sfAmount]; + auto const amount = get(ctx.tx[sfAmount]); if (amount.signum() <= 0 || !amount.native()) return temBAD_AMOUNT; @@ -2201,7 +2203,7 @@ TER XChainCreateAccountCommit::preclaim(PreclaimContext const& ctx) { STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge]; - STAmount const amount = ctx.tx[sfAmount]; + STAmount const amount = get(ctx.tx[sfAmount]); STAmount const reward = ctx.tx[sfSignatureReward]; auto const sleBridge = readBridge(ctx.view, bridgeSpec); @@ -2247,7 +2249,7 @@ XChainCreateAccountCommit::preclaim(PreclaimContext const& ctx) STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain); - if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue()) + if (bridgeSpec.issue(srcChain) != get(ctx.tx[sfAmount]).issue()) return tecXCHAIN_BAD_TRANSFER_ISSUE; if (!isXRP(bridgeSpec.issue(dstChain))) @@ -2262,7 +2264,7 @@ XChainCreateAccountCommit::doApply() PaymentSandbox psb(&ctx_.view()); AccountID const account = ctx_.tx[sfAccount]; - STAmount const amount = ctx_.tx[sfAmount]; + STAmount const amount = get(ctx_.tx[sfAmount]); STAmount const reward = ctx_.tx[sfSignatureReward]; STXChainBridge const bridge = ctx_.tx[sfXChainBridge]; diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index 9185365ff5a..b413a775bea 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -26,10 +26,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -146,7 +146,7 @@ accountHolds( FreezeHandling zeroIfFrozen, beast::Journal j); -[[nodiscard]] STAmount +[[nodiscard]] STMPTAmount accountHolds( ReadView const& view, AccountID const& account, @@ -235,7 +235,7 @@ forEachItemAfter( transferRate(ReadView const& view, AccountID const& issuer); [[nodiscard]] Rate -transferRateMPT(ReadView const& view, MPT const& id); +transferRate(ReadView const& view, MPTID const& id); /** Returns `true` if the directory is empty @param key The key of the directory @@ -448,11 +448,11 @@ rippleCredit( beast::Journal j); [[nodiscard]] TER -rippleMPTCredit( +rippleCredit( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, - STAmount saAmount, + STMPTAmount saAmount, beast::Journal j); [[nodiscard]] TER @@ -465,11 +465,11 @@ accountSend( WaiveTransferFee waiveFee = WaiveTransferFee::No); [[nodiscard]] TER -accountSendMPT( +accountSend( ApplyView& view, AccountID const& from, AccountID const& to, - const STAmount& saAmount, + const STMPTAmount& saAmount, beast::Journal j, WaiveTransferFee waiveFee = WaiveTransferFee::No); diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 02519df0925..22a8249758e 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -180,7 +180,7 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer) bool isGlobalFrozen(ReadView const& view, MPTIssue const& mpt) { - if (auto const sle = view.read(keylet::mptIssuance(mpt.mpt()))) + if (auto const sle = view.read(keylet::mptIssuance(mpt.getMptID()))) return sle->getFlags() & lsfMPTLocked; return false; } @@ -211,7 +211,7 @@ isIndividualFrozen( AccountID const& account, MPTIssue const& mpt) { - if (auto const sle = view.read(keylet::mptoken(mpt.mpt(), account))) + if (auto const sle = view.read(keylet::mptoken(mpt.getMptID(), account))) return sle->getFlags() & lsfMPTLocked; return false; } @@ -305,7 +305,7 @@ accountHolds( view, account, issue.currency, issue.account, zeroIfFrozen, j); } -STAmount +STMPTAmount accountHolds( ReadView const& view, AccountID const& account, @@ -314,9 +314,9 @@ accountHolds( AuthHandling zeroIfUnauthorized, beast::Journal j) { - STAmount amount; + STMPTAmount amount; - auto const sleMpt = view.read(keylet::mptoken(issue.mpt(), account)); + auto const sleMpt = view.read(keylet::mptoken(issue.getMptID(), account)); if (!sleMpt) amount.clear(issue); else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, issue)) @@ -326,7 +326,7 @@ accountHolds( auto const amt = sleMpt->getFieldU64(sfMPTAmount); auto const locked = sleMpt->getFieldU64(sfLockedAmount); if (amt > locked) - amount = STAmount{issue, amt - locked}; + amount = STMPTAmount{issue, amt - locked}; // only if auth check is needed, as it needs to do an additional read // operation @@ -564,7 +564,7 @@ transferRate(ReadView const& view, AccountID const& issuer) } Rate -transferRateMPT(ReadView const& view, MPT const& id) +transferRate(ReadView const& view, MPTID const& id) { auto const sle = view.read(keylet::mptIssuance(id)); @@ -900,8 +900,9 @@ trustCreate( bSetHigh ? sfHighLimit : sfLowLimit, saLimit); sleRippleState->setFieldAmount( bSetHigh ? sfLowLimit : sfHighLimit, - STAmount(Issue{ - saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID})); + STAmount( + {saBalance.getCurrency(), + bSetDst ? uSrcAccountID : uDstAccountID})); if (uQualityIn) sleRippleState->setFieldU32( @@ -1134,7 +1135,7 @@ rippleCredit( return tesSUCCESS; } - STAmount const saReceiverLimit(Issue{currency, uReceiverID}); + STAmount const saReceiverLimit({currency, uReceiverID}); STAmount saBalance{saAmount}; saBalance.setIssuer(noAccount()); @@ -1235,7 +1236,7 @@ accountSend( } else { - assert(saAmount >= beast::zero && !saAmount.isMPT()); + assert(saAmount >= beast::zero); } /* If we aren't sending anything or if the sender is the same as the @@ -1336,26 +1337,26 @@ accountSend( } static TER -rippleSendMPT( +rippleSend( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, - STAmount const& saAmount, - STAmount& saActual, + STMPTAmount const& saAmount, + STMPTAmount& saActual, beast::Journal j, WaiveTransferFee waiveFee) { assert(uSenderID != uReceiverID); - // Safe to get MPT since rippleSendMPT is only called by accountSendMPT + // Safe to get MPT since rippleSend is only called by accountSend auto const issuer = saAmount.getIssuer(); if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. auto const ter = - rippleMPTCredit(view, uSenderID, uReceiverID, saAmount, j); - if (view.rules().enabled(featureDeletableAccounts) && ter != tesSUCCESS) + rippleCredit(view, uSenderID, uReceiverID, saAmount, j); + if (ter != tesSUCCESS) return ter; saActual = saAmount; return tesSUCCESS; @@ -1363,14 +1364,12 @@ rippleSendMPT( // Sending 3rd party MPTs: transit. if (auto const sle = - view.read(keylet::mptIssuance(saAmount.mptIssue().mpt()))) + view.read(keylet::mptIssuance(saAmount.issue().getMptID()))) { saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount : multiply( - saAmount, - transferRateMPT( - view, static_cast(saAmount.mptIssue().mpt()))); + saAmount, transferRate(view, saAmount.issue().getMptID())); JLOG(j.debug()) << "rippleSend> " << to_string(uSenderID) << " - > " << to_string(uReceiverID) @@ -1378,26 +1377,26 @@ rippleSendMPT( << " cost=" << saActual.getFullText(); if (auto const terResult = - rippleMPTCredit(view, issuer, uReceiverID, saAmount, j); + rippleCredit(view, issuer, uReceiverID, saAmount, j); terResult != tesSUCCESS) return terResult; else - return rippleMPTCredit(view, uSenderID, issuer, saActual, j); + return rippleCredit(view, uSenderID, issuer, saActual, j); } return tecINTERNAL; } TER -accountSendMPT( +accountSend( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, - STAmount const& saAmount, + STMPTAmount const& saAmount, beast::Journal j, WaiveTransferFee waiveFee) { - assert(saAmount >= beast::zero && saAmount.isMPT()); + assert(saAmount >= beast::zero); /* If we aren't sending anything or if the sender is the same as the * receiver then we don't need to do anything. @@ -1405,9 +1404,10 @@ accountSendMPT( if (!saAmount || (uSenderID == uReceiverID)) return tesSUCCESS; - STAmount saActual{saAmount.asset()}; + // STAmount saActual{saAmount.asset()}; + STMPTAmount saActual{}; - return rippleSendMPT( + return rippleSend( view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); } @@ -1532,7 +1532,7 @@ issueIOU( // NIKB TODO: The limit uses the receiver's account as the issuer and // this is unnecessarily inefficient as copying which could be avoided // is now required. Consider available options. - STAmount const limit(Issue{issue.currency, account}); + STAmount const limit({issue.currency, account}); STAmount final_balance = amount; final_balance.setIssuer(noAccount()); @@ -1695,7 +1695,7 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account) TER requireAuth(ReadView const& view, MPTIssue const& mpt, AccountID const& account) { - auto const mptID = keylet::mptIssuance(mpt.mpt()); + auto const mptID = keylet::mptIssuance(mpt.getMptID()); if (auto const sle = view.read(mptID); sle && sle->getFieldU32(sfFlags) & lsfMPTRequireAuth) { @@ -1715,7 +1715,7 @@ canTransfer( AccountID const& from, AccountID const& to) { - auto const mptID = keylet::mptIssuance(mpt.mpt()); + auto const mptID = keylet::mptIssuance(mpt.getMptID()); if (auto const sle = view.read(mptID); sle && !(sle->getFieldU32(sfFlags) & lsfMPTCanTransfer)) { @@ -1851,14 +1851,14 @@ deleteAMMTrustLine( } TER -rippleMPTCredit( +rippleCredit( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, - STAmount saAmount, + STMPTAmount saAmount, beast::Journal j) { - auto const mptID = keylet::mptIssuance(saAmount.mptIssue().mpt()); + auto const mptID = keylet::mptIssuance(saAmount.issue().getMptID()); auto const issuer = saAmount.getIssuer(); if (uSenderID == issuer) { @@ -1866,7 +1866,7 @@ rippleMPTCredit( { sle->setFieldU64( sfOutstandingAmount, - sle->getFieldU64(sfOutstandingAmount) + saAmount.mpt().mpt()); + sle->getFieldU64(sfOutstandingAmount) + saAmount.value()); if (sle->getFieldU64(sfOutstandingAmount) > (*sle)[~sfMaximumAmount].value_or(maxMPTokenAmount)) @@ -1883,7 +1883,7 @@ rippleMPTCredit( if (auto sle = view.peek(mptokenID)) { auto const amt = sle->getFieldU64(sfMPTAmount); - auto const pay = saAmount.mpt().mpt(); + auto const pay = saAmount.value(); if (amt >= pay) { if (amt == pay) @@ -1895,6 +1895,8 @@ rippleMPTCredit( else return tecINSUFFICIENT_FUNDS; } + else + return tecNO_AUTH; } if (uReceiverID == issuer) @@ -1902,7 +1904,7 @@ rippleMPTCredit( if (auto sle = view.peek(mptID)) { auto const outstanding = sle->getFieldU64(sfOutstandingAmount); - auto const redeem = saAmount.mpt().mpt(); + auto const redeem = saAmount.value(); if (outstanding >= redeem) { sle->setFieldU64(sfOutstandingAmount, outstanding - redeem); @@ -1920,10 +1922,11 @@ rippleMPTCredit( if (auto sle = view.peek(mptokenID)) { sle->setFieldU64( - sfMPTAmount, - sle->getFieldU64(sfMPTAmount) + saAmount.mpt().mpt()); + sfMPTAmount, sle->getFieldU64(sfMPTAmount) + saAmount.value()); view.update(sle); } + else + return tecNO_AUTH; } return tesSUCCESS; } diff --git a/src/xrpld/rpc/DeliveredAmount.h b/src/xrpld/rpc/DeliveredAmount.h index 2ebadd38752..63e11b707a1 100644 --- a/src/xrpld/rpc/DeliveredAmount.h +++ b/src/xrpld/rpc/DeliveredAmount.h @@ -72,7 +72,7 @@ insertDeliveredAmount( std::shared_ptr const&, TxMeta const&); -std::optional +std::optional getDeliveredAmount( RPC::Context const& context, std::shared_ptr const& serializedTx, diff --git a/src/xrpld/rpc/MPTokenIssuanceID.h b/src/xrpld/rpc/MPTokenIssuanceID.h index f7f45fded3b..4f94e61fa86 100644 --- a/src/xrpld/rpc/MPTokenIssuanceID.h +++ b/src/xrpld/rpc/MPTokenIssuanceID.h @@ -37,7 +37,7 @@ canHaveMPTokenIssuanceID( std::shared_ptr const& serializedTx, TxMeta const& transactionMeta); -std::optional +std::optional getIDFromCreatedIssuance(TxMeta const& transactionMeta); void diff --git a/src/xrpld/rpc/detail/DeliveredAmount.cpp b/src/xrpld/rpc/detail/DeliveredAmount.cpp index 7874997e24f..4396fb291c7 100644 --- a/src/xrpld/rpc/detail/DeliveredAmount.cpp +++ b/src/xrpld/rpc/detail/DeliveredAmount.cpp @@ -44,7 +44,7 @@ namespace RPC { std::optional */ template -std::optional +std::optional getDeliveredAmount( GetLedgerIndex const& getLedgerIndex, GetCloseTime const& getCloseTime, @@ -174,7 +174,7 @@ insertDeliveredAmount( } template -static std::optional +static std::optional getDeliveredAmount( RPC::Context const& context, std::shared_ptr const& serializedTx, @@ -195,7 +195,7 @@ getDeliveredAmount( return {}; } -std::optional +std::optional getDeliveredAmount( RPC::Context const& context, std::shared_ptr const& serializedTx, diff --git a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp index 8de1e8f3344..c2aa3eea02a 100644 --- a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp +++ b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp @@ -47,7 +47,7 @@ canHaveMPTokenIssuanceID( return true; } -std::optional +std::optional getIDFromCreatedIssuance(TxMeta const& transactionMeta) { for (STObject const& node : transactionMeta.getNodes()) @@ -74,7 +74,7 @@ insertMPTokenIssuanceID( if (!canHaveMPTokenIssuanceID(transaction, transactionMeta)) return; - std::optional result = getIDFromCreatedIssuance(transactionMeta); + std::optional result = getIDFromCreatedIssuance(transactionMeta); if (result.has_value()) response[jss::mpt_issuance_id] = to_string(result.value()); } diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index 1fee84c683b..77938b4924a 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -200,7 +200,7 @@ checkPayment( if (!tx_json.isMember(jss::Amount)) return RPC::missing_field_error("tx_json.Amount"); - STAmount amount; + STEitherAmount amount; if (!amountFromJsonNoThrow(amount, tx_json[jss::Amount])) return RPC::invalid_field_error("tx_json.Amount"); @@ -213,7 +213,8 @@ checkPayment( if (!dstAccountID) return RPC::invalid_field_error("tx_json.Destination"); - if ((doPath == false) && params.isMember(jss::build_path)) + if (((doPath == false) && params.isMember(jss::build_path)) || + (params.isMember(jss::build_path) && !amount.isIssue())) return RPC::make_error( rpcINVALID_PARAMS, "Field 'build_path' not allowed in this context."); @@ -235,7 +236,7 @@ checkPayment( else { // If no SendMax, default to Amount with sender as issuer. - sendMax = amount; + sendMax = get(amount); sendMax.setIssuer(srcAddressID); } @@ -259,7 +260,7 @@ checkPayment( *dstAccountID, sendMax.issue().currency, sendMax.issue().account, - amount, + get(amount), std::nullopt, app); if (pf.findPaths(app.config().PATH_SEARCH_OLD)) diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 30b1113cd05..b8f5ffe104a 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -651,7 +651,7 @@ doLedgerEntry(RPC::JsonContext& context) context.params[jss::mpt_issuance]; if (unparsedMPTIssuanceID.isString()) { - uint192 mptIssuanceID; + MPTID mptIssuanceID; if (!mptIssuanceID.parseHex(unparsedMPTIssuanceID.asString())) { uNodeIndex = beast::zero; @@ -691,7 +691,7 @@ doLedgerEntry(RPC::JsonContext& context) context.params[jss::mptoken][jss::mpt_issuance_id] .asString(); - uint192 mptIssuanceID; + MPTID mptIssuanceID; if (!mptIssuanceID.parseHex(mptIssuanceIdStr)) Throw( "Cannot parse mpt_issuance_id");