diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index d36bf74c54..1eacb16385 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -632,6 +632,13 @@ to_string(base_uint const& a) return strHex(a.cbegin(), a.cend()); } +template +inline std::string +to_short_string(base_uint const& a) +{ + return to_string(a).substr(0, 8) + "..."; +} + template inline std::ostream& operator<<(std::ostream& out, base_uint const& u) diff --git a/include/xrpl/beast/utility/instrumentation.h b/include/xrpl/beast/utility/instrumentation.h index 72c48959a0..3594855eef 100644 --- a/include/xrpl/beast/utility/instrumentation.h +++ b/include/xrpl/beast/utility/instrumentation.h @@ -39,11 +39,16 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #endif #define XRPL_ASSERT ALWAYS_OR_UNREACHABLE +#define XRPL_ASSERT_PARTS(cond, function, description, ...) \ + XRPL_ASSERT(cond, function " : " description) // How to use the instrumentation macros: // // * XRPL_ASSERT if cond must be true but the line might not be reached during // fuzzing. Same like `assert` in normal use. +// * XRPL_ASSERT_PARTS is for convenience, and works like XRPL_ASSERT, but +// splits the message param into "function" and "description", then joins +// them with " : " before passing to XRPL_ASSERT. // * ALWAYS if cond must be true _and_ the line must be reached during fuzzing. // Same like `assert` in normal use. // * REACHABLE if the line must be reached during fuzzing diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 49bad8a076..1a49d9d09e 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 777cfa02ba..a72332b888 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -149,7 +150,9 @@ public: sMD_DeleteFinal = 0x04, // final value when it is deleted sMD_Create = 0x08, // value when it's created sMD_Always = 0x10, // value when node containing it is affected at all - sMD_BaseTen = 0x20, + sMD_BaseTen = 0x20, // value is treated as base 10, overriding behavior + sMD_PseudoAccount = 0x40, // if this field is set in an ACCOUNT_ROOT + // _only_, then it is a pseudo-account sMD_Default = sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create }; @@ -306,6 +309,7 @@ public: private: static int num; static std::map knownCodeToField; + static std::map knownNameToField; }; /** A field with a type known at compile time. */ diff --git a/include/xrpl/protocol/STLedgerEntry.h b/include/xrpl/protocol/STLedgerEntry.h index 3609a04d4b..571c7af5fe 100644 --- a/include/xrpl/protocol/STLedgerEntry.h +++ b/include/xrpl/protocol/STLedgerEntry.h @@ -26,7 +26,9 @@ namespace ripple { class Rules; +namespace test { class Invariants_test; +} class STLedgerEntry final : public STObject, public CountedObject { @@ -36,6 +38,8 @@ class STLedgerEntry final : public STObject, public CountedObject public: using pointer = std::shared_ptr; using ref = std::shared_ptr const&; + using const_pointer = std::shared_ptr; + using const_ref = std::shared_ptr const&; /** Create an empty object with the given key and type. */ explicit STLedgerEntry(Keylet const& k); @@ -54,7 +58,7 @@ public: getText() const override; Json::Value - getJson(JsonOptions options) const override; + getJson(JsonOptions options = JsonOptions::none) const override; /** Returns the 'key' (or 'index') of this item. The key identifies this entry's position in @@ -84,7 +88,8 @@ private: void setSLEType(); - friend Invariants_test; // this test wants access to the private type_ + friend test::Invariants_test; // this test wants access to the private + // type_ STBase* copy(std::size_t n, void* buf) const override; diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index 6cd083ef85..b3cb561390 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -34,6 +33,7 @@ #include #include #include +#include #include #include diff --git a/include/xrpl/protocol/STValidation.h b/include/xrpl/protocol/STValidation.h index 11ec733c01..f87923c940 100644 --- a/include/xrpl/protocol/STValidation.h +++ b/include/xrpl/protocol/STValidation.h @@ -22,10 +22,10 @@ #include #include -#include #include #include #include +#include #include #include diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 505000cfd6..b2da99594a 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -130,6 +130,8 @@ constexpr std::uint32_t tfTrustSetPermissionMask = (~tfTrustSetMask) & (~tfTrust // EnableAmendment flags: constexpr std::uint32_t tfGotMajority = 0x00010000; constexpr std::uint32_t tfLostMajority = 0x00020000; +constexpr std::uint32_t tfChangeMask = + ~( tfUniversal | tfGotMajority | tfLostMajority); // PaymentChannelClaim flags: constexpr std::uint32_t tfRenew = 0x00010000; @@ -144,7 +146,8 @@ constexpr std::uint32_t const tfTransferable = 0x00000008; constexpr std::uint32_t const tfMutable = 0x00000010; // MPTokenIssuanceCreate flags: -// NOTE - there is intentionally no flag here for lsfMPTLocked, which this transaction cannot mutate. +// NOTE - there is intentionally no flag here for lsfMPTLocked, which +// this transaction cannot mutate. constexpr std::uint32_t const tfMPTCanLock = lsfMPTCanLock; constexpr std::uint32_t const tfMPTRequireAuth = lsfMPTRequireAuth; constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow; diff --git a/include/xrpl/protocol/TxFormats.h b/include/xrpl/protocol/TxFormats.h index 70b721a3d7..60066536ce 100644 --- a/include/xrpl/protocol/TxFormats.h +++ b/include/xrpl/protocol/TxFormats.h @@ -59,7 +59,8 @@ enum TxType : std::uint16_t #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) tag = value, +#define TRANSACTION(tag, value, ...) \ + tag = value, #include diff --git a/include/xrpl/protocol/FeeUnits.h b/include/xrpl/protocol/Units.h similarity index 64% rename from include/xrpl/protocol/FeeUnits.h rename to include/xrpl/protocol/Units.h index c6949a434c..f92623eec6 100644 --- a/include/xrpl/protocol/FeeUnits.h +++ b/include/xrpl/protocol/Units.h @@ -16,8 +16,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef BASICS_FEES_H_INCLUDED -#define BASICS_FEES_H_INCLUDED +#ifndef PROTOCOL_UNITS_H_INCLUDED +#define PROTOCOL_UNITS_H_INCLUDED #include #include @@ -38,23 +38,23 @@ namespace ripple { -namespace feeunit { +namespace unit { /** "drops" are the smallest divisible amount of XRP. This is what most of the code uses. */ struct dropTag; -/** "fee units" calculations are a not-really-unitless value that is used - to express the cost of a given transaction vs. a reference transaction. - They are primarily used by the Transactor classes. */ -struct feeunitTag; /** "fee levels" are used by the transaction queue to compare the relative cost of transactions that require different levels of effort to process. See also: src/ripple/app/misc/FeeEscalation.md#fee-level */ struct feelevelTag; -/** unitless values are plain scalars wrapped in a TaggedFee. They are +/** unitless values are plain scalars wrapped in a ValueUnit. They are used for calculations in this header. */ struct unitlessTag; +/** Units to represent basis points (bips) and 1/10 basis points */ +class BipsTag; +class TenthBipsTag; + template using enable_if_unit_t = typename std::enable_if_t< std::is_class_v && std::is_object_v && @@ -62,32 +62,33 @@ using enable_if_unit_t = typename std::enable_if_t< /** `is_usable_unit_v` is checked to ensure that only values with known valid type tags can be used (sometimes transparently) in - non-fee contexts. At the time of implementation, this includes + non-unit contexts. At the time of implementation, this includes all known tags, but more may be added in the future, and they should not be added automatically unless determined to be appropriate. */ template > constexpr bool is_usable_unit_v = - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v; + std::is_same_v || + std::is_same_v || + std::is_same_v; template -class TaggedFee : private boost::totally_ordered>, - private boost::additive>, - private boost::equality_comparable, T>, - private boost::dividable, T>, - private boost::modable, T>, - private boost::unit_steppable> +class ValueUnit : private boost::totally_ordered>, + private boost::additive>, + private boost::equality_comparable, T>, + private boost::dividable, T>, + private boost::modable, T>, + private boost::unit_steppable> { public: using unit_type = UnitTag; using value_type = T; private: - value_type fee_; + value_type value_; protected: template @@ -95,44 +96,44 @@ protected: std::is_arithmetic_v && std::is_arithmetic_v && std::is_convertible_v; - template > - static constexpr bool is_compatiblefee_v = - is_compatible_v && - std::is_same_v; + template > + static constexpr bool is_compatiblevalue_v = + is_compatible_v && + std::is_same_v; template using enable_if_compatible_t = typename std::enable_if_t>; - template - using enable_if_compatiblefee_t = - typename std::enable_if_t>; + template + using enable_if_compatiblevalue_t = + typename std::enable_if_t>; public: - TaggedFee() = default; - constexpr TaggedFee(TaggedFee const& other) = default; - constexpr TaggedFee& - operator=(TaggedFee const& other) = default; + ValueUnit() = default; + constexpr ValueUnit(ValueUnit const& other) = default; + constexpr ValueUnit& + operator=(ValueUnit const& other) = default; - constexpr explicit TaggedFee(beast::Zero) : fee_(0) + constexpr explicit ValueUnit(beast::Zero) : value_(0) { } - constexpr TaggedFee& + constexpr ValueUnit& operator=(beast::Zero) { - fee_ = 0; + value_ = 0; return *this; } - constexpr explicit TaggedFee(value_type fee) : fee_(fee) + constexpr explicit ValueUnit(value_type value) : value_(value) { } - TaggedFee& - operator=(value_type fee) + constexpr ValueUnit& + operator=(value_type value) { - fee_ = fee; + value_ = value; return *this; } @@ -144,153 +145,181 @@ public: class = std::enable_if_t< is_compatible_v && is_safetocasttovalue_v>> - constexpr TaggedFee(TaggedFee const& fee) - : TaggedFee(safe_cast(fee.fee())) + constexpr ValueUnit(ValueUnit const& value) + : ValueUnit(safe_cast(value.value())) { } - constexpr TaggedFee + constexpr ValueUnit + operator+(value_type const& rhs) const + { + return ValueUnit{value_ + rhs}; + } + + friend constexpr ValueUnit + operator+(value_type lhs, ValueUnit const& rhs) + { + // addition is commutative + return rhs + lhs; + } + + constexpr ValueUnit + operator-(value_type const& rhs) const + { + return ValueUnit{value_ - rhs}; + } + + friend constexpr ValueUnit + operator-(value_type lhs, ValueUnit const& rhs) + { + // subtraction is NOT commutative, but (lhs + (-rhs)) is addition, which + // is + return -rhs + lhs; + } + + constexpr ValueUnit operator*(value_type const& rhs) const { - return TaggedFee{fee_ * rhs}; + return ValueUnit{value_ * rhs}; } - friend constexpr TaggedFee - operator*(value_type lhs, TaggedFee const& rhs) + friend constexpr ValueUnit + operator*(value_type lhs, ValueUnit const& rhs) { // multiplication is commutative return rhs * lhs; } constexpr value_type - operator/(TaggedFee const& rhs) const + operator/(ValueUnit const& rhs) const { - return fee_ / rhs.fee_; + return value_ / rhs.value_; } - TaggedFee& - operator+=(TaggedFee const& other) + ValueUnit& + operator+=(ValueUnit const& other) { - fee_ += other.fee(); + value_ += other.value(); return *this; } - TaggedFee& - operator-=(TaggedFee const& other) + ValueUnit& + operator-=(ValueUnit const& other) { - fee_ -= other.fee(); + value_ -= other.value(); return *this; } - TaggedFee& + ValueUnit& operator++() { - ++fee_; + ++value_; return *this; } - TaggedFee& + ValueUnit& operator--() { - --fee_; + --value_; return *this; } - TaggedFee& + ValueUnit& operator*=(value_type const& rhs) { - fee_ *= rhs; + value_ *= rhs; return *this; } - TaggedFee& + ValueUnit& operator/=(value_type const& rhs) { - fee_ /= rhs; + value_ /= rhs; return *this; } template - std::enable_if_t, TaggedFee&> + std::enable_if_t, ValueUnit&> operator%=(value_type const& rhs) { - fee_ %= rhs; + value_ %= rhs; return *this; } - TaggedFee + ValueUnit operator-() const { static_assert( - std::is_signed_v, "- operator illegal on unsigned fee types"); - return TaggedFee{-fee_}; + std::is_signed_v, "- operator illegal on unsigned value types"); + return ValueUnit{-value_}; } - bool - operator==(TaggedFee const& other) const + constexpr bool + operator==(ValueUnit const& other) const { - return fee_ == other.fee_; + return value_ == other.value_; } template > - bool - operator==(TaggedFee const& other) const + constexpr bool + operator==(ValueUnit const& other) const { - return fee_ == other.fee(); + return value_ == other.value(); } - bool + constexpr bool operator==(value_type other) const { - return fee_ == other; + return value_ == other; } template > - bool - operator!=(TaggedFee const& other) const + constexpr bool + operator!=(ValueUnit const& other) const { return !operator==(other); } - bool - operator<(TaggedFee const& other) const + constexpr bool + operator<(ValueUnit const& other) const { - return fee_ < other.fee_; + return value_ < other.value_; } /** Returns true if the amount is not zero */ explicit constexpr operator bool() const noexcept { - return fee_ != 0; + return value_ != 0; } /** Return the sign of the amount */ constexpr int signum() const noexcept { - return (fee_ < 0) ? -1 : (fee_ ? 1 : 0); + return (value_ < 0) ? -1 : (value_ ? 1 : 0); } /** Returns the number of drops */ + // TODO: Move this to a new class, maybe with the old "TaggedFee" name constexpr value_type fee() const { - return fee_; + return value_; } template constexpr double - decimalFromReference(TaggedFee reference) const + decimalFromReference(ValueUnit reference) const { - return static_cast(fee_) / reference.fee(); + return static_cast(value_) / reference.value(); } // `is_usable_unit_v` is checked to ensure that only values with // known valid type tags can be converted to JSON. At the time // of implementation, that includes all known tags, but more may // be added in the future. - std::enable_if_t, Json::Value> + std::enable_if_t, Json::Value> jsonClipped() const { if constexpr (std::is_integral_v) @@ -303,15 +332,15 @@ public: constexpr auto min = std::numeric_limits::min(); constexpr auto max = std::numeric_limits::max(); - if (fee_ < min) + if (value_ < min) return min; - if (fee_ > max) + if (value_ > max) return max; - return static_cast(fee_); + return static_cast(value_); } else { - return fee_; + return value_; } } @@ -322,30 +351,30 @@ public: constexpr value_type value() const { - return fee_; + return value_; } friend std::istream& - operator>>(std::istream& s, TaggedFee& val) + operator>>(std::istream& s, ValueUnit& val) { - s >> val.fee_; + s >> val.value_; return s; } }; -// Output Fees as just their numeric value. +// Output Values as just their numeric value. template std::basic_ostream& -operator<<(std::basic_ostream& os, TaggedFee const& q) +operator<<(std::basic_ostream& os, ValueUnit const& q) { return os << q.value(); } template std::string -to_string(TaggedFee const& amount) +to_string(ValueUnit const& amount) { - return std::to_string(amount.fee()); + return std::to_string(amount.value()); } template > @@ -408,10 +437,10 @@ using enable_muldiv_commute_t = typename std::enable_if_t>; template -TaggedFee +ValueUnit scalar(T value) { - return TaggedFee{value}; + return ValueUnit{value}; } template < @@ -422,18 +451,17 @@ template < std::optional mulDivU(Source1 value, Dest mul, Source2 div) { - // Fees can never be negative in any context. + // values can never be negative in any context. if (value.value() < 0 || mul.value() < 0 || div.value() < 0) { // split the asserts so if one hits, the user can tell which // without a debugger. XRPL_ASSERT( - value.value() >= 0, - "ripple::feeunit::mulDivU : minimum value input"); + value.value() >= 0, "ripple::unit::mulDivU : minimum value input"); XRPL_ASSERT( - mul.value() >= 0, "ripple::feeunit::mulDivU : minimum mul input"); + mul.value() >= 0, "ripple::unit::mulDivU : minimum mul input"); XRPL_ASSERT( - div.value() >= 0, "ripple::feeunit::mulDivU : minimum div input"); + div.value() >= 0, "ripple::unit::mulDivU : minimum div input"); return std::nullopt; } @@ -466,46 +494,57 @@ mulDivU(Source1 value, Dest mul, Source2 div) return Dest{static_cast(quotient)}; } -} // namespace feeunit +} // namespace unit +// Fee Levels template -using FeeLevel = feeunit::TaggedFee; +using FeeLevel = unit::ValueUnit; using FeeLevel64 = FeeLevel; using FeeLevelDouble = FeeLevel; +// Basis points (Bips) +template +using Bips = unit::ValueUnit; +using Bips16 = Bips; +using Bips32 = Bips; +template +using TenthBips = unit::ValueUnit; +using TenthBips16 = TenthBips; +using TenthBips32 = TenthBips; + template < class Source1, class Source2, class Dest, - class = feeunit::enable_muldiv_t> + class = unit::enable_muldiv_t> std::optional mulDiv(Source1 value, Dest mul, Source2 div) { - return feeunit::mulDivU(value, mul, div); + return unit::mulDivU(value, mul, div); } template < class Source1, class Source2, class Dest, - class = feeunit::enable_muldiv_commute_t> + class = unit::enable_muldiv_commute_t> std::optional mulDiv(Dest value, Source1 mul, Source2 div) { // Multiplication is commutative - return feeunit::mulDivU(mul, value, div); + return unit::mulDivU(mul, value, div); } -template > +template > std::optional mulDiv(std::uint64_t value, Dest mul, std::uint64_t div) { // Give the scalars a non-tag so the // unit-handling version gets called. - return feeunit::mulDivU(feeunit::scalar(value), mul, feeunit::scalar(div)); + return unit::mulDivU(unit::scalar(value), mul, unit::scalar(div)); } -template > +template > std::optional mulDiv(Dest value, std::uint64_t mul, std::uint64_t div) { @@ -516,13 +555,13 @@ mulDiv(Dest value, std::uint64_t mul, std::uint64_t div) template < class Source1, class Source2, - class = feeunit::enable_muldiv_sources_t> + class = unit::enable_muldiv_sources_t> std::optional mulDiv(Source1 value, std::uint64_t mul, Source2 div) { // Give the scalars a dimensionless unit so the // unit-handling version gets called. - auto unitresult = feeunit::mulDivU(value, feeunit::scalar(mul), div); + auto unitresult = unit::mulDivU(value, unit::scalar(mul), div); if (!unitresult) return std::nullopt; @@ -533,7 +572,7 @@ mulDiv(Source1 value, std::uint64_t mul, Source2 div) template < class Source1, class Source2, - class = feeunit::enable_muldiv_sources_t> + class = unit::enable_muldiv_sources_t> std::optional mulDiv(std::uint64_t value, Source1 mul, Source2 div) { @@ -553,6 +592,16 @@ safe_cast(Src s) noexcept return Dest{safe_cast(s.value())}; } +template +constexpr std::enable_if_t< + std::is_integral_v && std::is_integral_v, + Dest> +safe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{safe_cast(s)}; +} + template constexpr std::enable_if_t< std::is_same_v && @@ -565,6 +614,16 @@ unsafe_cast(Src s) noexcept return Dest{unsafe_cast(s.value())}; } +template +constexpr std::enable_if_t< + std::is_integral_v && std::is_integral_v, + Dest> +unsafe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{unsafe_cast(s)}; +} + } // namespace ripple -#endif // BASICS_FEES_H_INCLUDED +#endif // PROTOCOL_UNITS_H_INCLUDED diff --git a/include/xrpl/protocol/XRPAmount.h b/include/xrpl/protocol/XRPAmount.h index 332735dc6f..a7a013d625 100644 --- a/include/xrpl/protocol/XRPAmount.h +++ b/include/xrpl/protocol/XRPAmount.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -42,7 +42,7 @@ class XRPAmount : private boost::totally_ordered, private boost::additive { public: - using unit_type = feeunit::dropTag; + using unit_type = unit::dropTag; using value_type = std::int64_t; private: diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 63bc52de6a..40d118c684 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -172,7 +172,8 @@ TYPED_SFIELD(sfNFTokenID, UINT256, 10) TYPED_SFIELD(sfEmitParentTxnID, UINT256, 11) TYPED_SFIELD(sfEmitNonce, UINT256, 12) TYPED_SFIELD(sfEmitHookHash, UINT256, 13) -TYPED_SFIELD(sfAMMID, UINT256, 14) +TYPED_SFIELD(sfAMMID, UINT256, 14, + SField::sMD_PseudoAccount |SField::sMD_Default) // 256-bit (uncommon) TYPED_SFIELD(sfBookDirectory, UINT256, 16) @@ -194,7 +195,8 @@ TYPED_SFIELD(sfHookHash, UINT256, 31) TYPED_SFIELD(sfHookNamespace, UINT256, 32) TYPED_SFIELD(sfHookSetTxnID, UINT256, 33) TYPED_SFIELD(sfDomainID, UINT256, 34) -TYPED_SFIELD(sfVaultID, UINT256, 35) +TYPED_SFIELD(sfVaultID, UINT256, 35, + SField::sMD_PseudoAccount | SField::sMD_Default) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 0f614df692..b61bf3135f 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -22,14 +22,47 @@ #endif /** - * TRANSACTION(tag, value, name, delegatable, fields) + * TRANSACTION(tag, value, name, delegatable, privileges, fields) + * + * To ease maintenance, you may replace any unneeded values with "..." + * e.g. #define TRANSACTION(tag, value, name, ...) * * You must define a transactor class in the `ripple` namespace named `name`, - * and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`. + * and include its header alongside the TRANSACTOR definition using this + * format: + * #if TRANSACTION_INCLUDE + * # include + * #endif + * + * The `privileges` parameter of the TRANSACTION macro is a bitfield + * defining which operations the transaction can perform. + * + * The values are only used in InvariantCheck.cpp + * Valid values are + noPriv - The transaction can not do any of the enumerated operations + createAcct - The transaction can create a new ACCOUNT_ROOT object. + createPseudoAcct - The transaction can create a pseudo account, + which implies createAcct + mustDeleteAcct - The transaction must delete an ACCOUNT_ROOT object + mayDeleteAcct - The transaction may delete an ACCOUNT_ROOT object, + but does not have to + overrideFreeze - The transaction can override some freeze rules + changeNFTCounts - The transaction can mint or burn an NFT + createMPTIssuance - The transaction can create a new MPT issuance + destroyMPTIssuance - The transaction can destroy an MPT issuance + mustAuthorizeMPT - The transaction MUST create or delete an MPT + object (except by issuer) + mayAuthorizeMPT - The transaction MAY create or delete an MPT + object (except by issuer) */ /** This transaction type executes a payment. */ -TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttPAYMENT, 0, Payment, + Delegation::delegatable, + createAcct, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfSendMax, soeOPTIONAL, soeMPTSupported}, @@ -41,7 +74,12 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({ })) /** This transaction type creates an escrow object. */ -TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, + Delegation::delegatable, + noPriv, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfCondition, soeOPTIONAL}, @@ -51,7 +89,9 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({ })) /** This transaction type completes an existing escrow. */ -TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({ +TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, + Delegation::delegatable, + noPriv, ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, {sfFulfillment, soeOPTIONAL}, @@ -61,7 +101,12 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({ /** This transaction type adjusts various account settings. */ -TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttACCOUNT_SET, 3, AccountSet, + Delegation::notDelegatable, + noPriv, ({ {sfEmailHash, soeOPTIONAL}, {sfWalletLocator, soeOPTIONAL}, {sfWalletSize, soeOPTIONAL}, @@ -75,20 +120,35 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, ({ })) /** This transaction type cancels an existing escrow. */ -TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, + Delegation::delegatable, + noPriv, ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, })) /** This transaction type sets or clears an account's "regular key". */ -TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::notDelegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, + Delegation::notDelegatable, + noPriv, ({ {sfRegularKey, soeOPTIONAL}, })) // 6 deprecated /** This transaction type creates an offer to trade one asset for another. */ -TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, + Delegation::delegatable, + noPriv, ({ {sfTakerPays, soeREQUIRED}, {sfTakerGets, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, @@ -96,14 +156,24 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, ({ })) /** This transaction type cancels existing offers to trade one asset for another. */ -TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, + Delegation::delegatable, + noPriv, ({ {sfOfferSequence, soeREQUIRED}, })) // 9 deprecated /** This transaction type creates a new set of tickets. */ -TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, + Delegation::delegatable, + noPriv, ({ {sfTicketCount, soeREQUIRED}, })) @@ -112,13 +182,23 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, ({ /** This transaction type modifies the signer list associated with an account. */ // The SignerEntries are optional because a SignerList is deleted by // setting the SignerQuorum to zero and omitting SignerEntries. -TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, + Delegation::notDelegatable, + noPriv, ({ {sfSignerQuorum, soeREQUIRED}, {sfSignerEntries, soeOPTIONAL}, })) /** This transaction type creates a new unidirectional XRP payment channel. */ -TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, + Delegation::delegatable, + noPriv, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfSettleDelay, soeREQUIRED}, @@ -128,14 +208,18 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, })) /** This transaction type funds an existing unidirectional XRP payment channel. */ -TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::delegatable, ({ +TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, + Delegation::delegatable, + noPriv, ({ {sfChannel, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, })) /** This transaction type submits a claim against an existing unidirectional payment channel. */ -TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, ({ +TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, + Delegation::delegatable, + noPriv, ({ {sfChannel, soeREQUIRED}, {sfAmount, soeOPTIONAL}, {sfBalance, soeOPTIONAL}, @@ -145,7 +229,12 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, ( })) /** This transaction type creates a new check. */ -TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, + Delegation::delegatable, + noPriv, ({ {sfDestination, soeREQUIRED}, {sfSendMax, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, @@ -154,19 +243,34 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, ({ })) /** This transaction type cashes an existing check. */ -TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCHECK_CASH, 17, CheckCash, + Delegation::delegatable, + noPriv, ({ {sfCheckID, soeREQUIRED}, {sfAmount, soeOPTIONAL}, {sfDeliverMin, soeOPTIONAL}, })) /** This transaction type cancels an existing check. */ -TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, + Delegation::delegatable, + noPriv, ({ {sfCheckID, soeREQUIRED}, })) /** This transaction type grants or revokes authorization to transfer funds. */ -TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, + Delegation::delegatable, + noPriv, ({ {sfAuthorize, soeOPTIONAL}, {sfUnauthorize, soeOPTIONAL}, {sfAuthorizeCredentials, soeOPTIONAL}, @@ -174,14 +278,24 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, ({ })) /** This transaction type modifies a trustline between two accounts. */ -TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttTRUST_SET, 20, TrustSet, + Delegation::delegatable, + noPriv, ({ {sfLimitAmount, soeOPTIONAL}, {sfQualityIn, soeOPTIONAL}, {sfQualityOut, soeOPTIONAL}, })) /** This transaction type deletes an existing account. */ -TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, + Delegation::notDelegatable, + mustDeleteAcct, ({ {sfDestination, soeREQUIRED}, {sfDestinationTag, soeOPTIONAL}, {sfCredentialIDs, soeOPTIONAL}, @@ -190,7 +304,12 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, ({ // 22 reserved /** This transaction mints a new NFT. */ -TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, + Delegation::delegatable, + changeNFTCounts, ({ {sfNFTokenTaxon, soeREQUIRED}, {sfTransferFee, soeOPTIONAL}, {sfIssuer, soeOPTIONAL}, @@ -201,13 +320,23 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, ({ })) /** This transaction burns (i.e. destroys) an existing NFT. */ -TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, + Delegation::delegatable, + changeNFTCounts, ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, })) /** This transaction creates a new offer to buy or sell an NFT. */ -TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, + Delegation::delegatable, + noPriv, ({ {sfNFTokenID, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfDestination, soeOPTIONAL}, @@ -216,25 +345,45 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegata })) /** This transaction cancels an existing offer to buy or sell an existing NFT. */ -TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, + Delegation::delegatable, + noPriv, ({ {sfNFTokenOffers, soeREQUIRED}, })) /** This transaction accepts an existing offer to buy or sell an existing NFT. */ -TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, + Delegation::delegatable, + noPriv, ({ {sfNFTokenBuyOffer, soeOPTIONAL}, {sfNFTokenSellOffer, soeOPTIONAL}, {sfNFTokenBrokerFee, soeOPTIONAL}, })) /** This transaction claws back issued tokens. */ -TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCLAWBACK, 30, Clawback, + Delegation::delegatable, + noPriv, ({ {sfAmount, soeREQUIRED, soeMPTSupported}, {sfHolder, soeOPTIONAL}, })) /** This transaction claws back tokens from an AMM pool. */ -TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, + Delegation::delegatable, + mayDeleteAcct | overrideFreeze, ({ {sfHolder, soeREQUIRED}, {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -242,14 +391,24 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, ({ })) /** This transaction type creates an AMM instance */ -TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMM_CREATE, 35, AMMCreate, + Delegation::delegatable, + createPseudoAcct, ({ {sfAmount, soeREQUIRED}, {sfAmount2, soeREQUIRED}, {sfTradingFee, soeREQUIRED}, })) /** This transaction type deposits into an AMM instance */ -TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, + Delegation::delegatable, + noPriv, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -260,7 +419,12 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, ({ })) /** This transaction type withdraws from an AMM instance */ -TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, + Delegation::delegatable, + mayDeleteAcct, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -270,14 +434,24 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, ({ })) /** This transaction type votes for the trading fee */ -TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMM_VOTE, 38, AMMVote, + Delegation::delegatable, + noPriv, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfTradingFee, soeREQUIRED}, })) /** This transaction type bids for the auction slot */ -TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMM_BID, 39, AMMBid, + Delegation::delegatable, + noPriv, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, {sfBidMin, soeOPTIONAL}, @@ -286,20 +460,32 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, ({ })) /** This transaction type deletes AMM in the empty state */ -TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMM_DELETE, 40, AMMDelete, + Delegation::delegatable, + mustDeleteAcct, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, })) /** This transactions creates a crosschain sequence number */ -TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, + Delegation::delegatable, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, {sfOtherChainSource, soeREQUIRED}, })) /** This transactions initiates a crosschain transaction */ -TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, + Delegation::delegatable, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -307,7 +493,9 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, ({ })) /** This transaction completes a crosschain transaction */ -TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, + Delegation::delegatable, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, {sfDestination, soeREQUIRED}, @@ -316,7 +504,9 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, ({ })) /** This transaction initiates a crosschain account create transaction */ -TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, + Delegation::delegatable, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -324,7 +514,9 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Deleg })) /** This transaction adds an attestation to a claim */ -TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, + Delegation::delegatable, + createAcct, ({ {sfXChainBridge, soeREQUIRED}, {sfAttestationSignerAccount, soeREQUIRED}, @@ -340,7 +532,10 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Deleg })) /** This transaction adds an attestation to an account */ -TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, + XChainAddAccountCreateAttestation, + Delegation::delegatable, + createAcct, ({ {sfXChainBridge, soeREQUIRED}, {sfAttestationSignerAccount, soeREQUIRED}, @@ -357,31 +552,47 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateA })) /** This transaction modifies a sidechain */ -TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, + Delegation::delegatable, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeOPTIONAL}, {sfMinAccountCreateAmount, soeOPTIONAL}, })) /** This transactions creates a sidechain */ -TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, Delegation::delegatable, ({ +TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, + Delegation::delegatable, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, {sfMinAccountCreateAmount, soeOPTIONAL}, })) /** This transaction type creates or updates a DID */ -TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttDID_SET, 49, DIDSet, + Delegation::delegatable, + noPriv, ({ {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, {sfData, soeOPTIONAL}, })) /** This transaction type deletes a DID */ -TRANSACTION(ttDID_DELETE, 50, DIDDelete, Delegation::delegatable, ({})) +TRANSACTION(ttDID_DELETE, 50, DIDDelete, + Delegation::delegatable, + noPriv, ({})) /** This transaction type creates an Oracle instance */ -TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttORACLE_SET, 51, OracleSet, + Delegation::delegatable, + noPriv, ({ {sfOracleDocumentID, soeREQUIRED}, {sfProvider, soeOPTIONAL}, {sfURI, soeOPTIONAL}, @@ -391,18 +602,33 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, ({ })) /** This transaction type deletes an Oracle instance */ -TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, + Delegation::delegatable, + noPriv, ({ {sfOracleDocumentID, soeREQUIRED}, })) /** This transaction type fixes a problem in the ledger state */ -TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, + Delegation::delegatable, + noPriv, ({ {sfLedgerFixType, soeREQUIRED}, {sfOwner, soeOPTIONAL}, })) /** This transaction type creates a MPTokensIssuance instance */ -TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, + Delegation::delegatable, + createMPTIssuance, ({ {sfAssetScale, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL}, {sfMaximumAmount, soeOPTIONAL}, @@ -410,24 +636,44 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::de })) /** This transaction type destroys a MPTokensIssuance instance */ -TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, + Delegation::delegatable, + destroyMPTIssuance, ({ {sfMPTokenIssuanceID, soeREQUIRED}, })) /** This transaction type sets flags on a MPTokensIssuance or MPToken instance */ -TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, + Delegation::delegatable, + noPriv, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, })) /** This transaction type authorizes a MPToken instance */ -TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, + Delegation::delegatable, + mustAuthorizeMPT, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, })) /** This transaction type create an Credential instance */ -TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, + Delegation::delegatable, + noPriv, ({ {sfSubject, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, @@ -435,44 +681,73 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, })) /** This transaction type accept an Credential object */ -TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, Delegation::delegatable, ({ +TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, + Delegation::delegatable, + noPriv, ({ {sfIssuer, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, })) /** This transaction type delete an Credential object */ -TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, Delegation::delegatable, ({ +TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, + Delegation::delegatable, + noPriv, ({ {sfSubject, soeOPTIONAL}, {sfIssuer, soeOPTIONAL}, {sfCredentialType, soeREQUIRED}, })) /** This transaction type modify a NFToken */ -TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, + Delegation::delegatable, + noPriv, ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, {sfURI, soeOPTIONAL}, })) /** This transaction type creates or modifies a Permissioned Domain */ -TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, + Delegation::delegatable, + noPriv, ({ {sfDomainID, soeOPTIONAL}, {sfAcceptedCredentials, soeREQUIRED}, })) /** This transaction type deletes a Permissioned Domain */ -TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, + Delegation::delegatable, + noPriv, ({ {sfDomainID, soeREQUIRED}, })) /** This transaction type delegates authorized account specified permissions */ -TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::notDelegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, + Delegation::notDelegatable, + noPriv, ({ {sfAuthorize, soeREQUIRED}, {sfPermissions, soeREQUIRED}, })) /** This transaction creates a single asset vault. */ -TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, + Delegation::delegatable, + createPseudoAcct | createMPTIssuance, ({ {sfAsset, soeREQUIRED, soeMPTSupported}, {sfAssetsMaximum, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, @@ -482,7 +757,12 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ })) /** This transaction updates a single asset vault. */ -TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttVAULT_SET, 66, VaultSet, + Delegation::delegatable, + noPriv, ({ {sfVaultID, soeREQUIRED}, {sfAssetsMaximum, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, // PermissionedDomainID @@ -490,25 +770,45 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({ })) /** This transaction deletes a single asset vault. */ -TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, + Delegation::delegatable, + mustDeleteAcct | destroyMPTIssuance, ({ {sfVaultID, soeREQUIRED}, })) /** This transaction trades assets for shares with a vault. */ -TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, + Delegation::delegatable, + mayAuthorizeMPT, ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, })) /** This transaction trades shares for assets with a vault. */ -TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, + Delegation::delegatable, + noPriv, ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfDestination, soeOPTIONAL}, })) /** This transaction claws back tokens from a vault. */ -TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::delegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, + Delegation::delegatable, + noPriv, ({ {sfVaultID, soeREQUIRED}, {sfHolder, soeREQUIRED}, {sfAmount, soeOPTIONAL, soeMPTSupported}, @@ -518,7 +818,12 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::delegatable, ({ For details, see: https://xrpl.org/amendments.html */ -TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, ({ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttAMENDMENT, 100, EnableAmendment, + Delegation::notDelegatable, + noPriv, ({ {sfLedgerSequence, soeREQUIRED}, {sfAmendment, soeREQUIRED}, })) @@ -526,7 +831,9 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, ({ /** This system-generated transaction type is used to update the network's fee settings. For details, see: https://xrpl.org/fee-voting.html */ -TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({ +TRANSACTION(ttFEE, 101, SetFee, + Delegation::notDelegatable, + noPriv, ({ {sfLedgerSequence, soeOPTIONAL}, // Old version uses raw numbers {sfBaseFee, soeOPTIONAL}, @@ -543,7 +850,9 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({ For details, see: https://xrpl.org/negative-unl.html */ -TRANSACTION(ttUNL_MODIFY, 102, UNLModify, Delegation::notDelegatable, ({ +TRANSACTION(ttUNL_MODIFY, 102, UNLModify, + Delegation::notDelegatable, + noPriv, ({ {sfUNLModifyDisabling, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED}, {sfUNLModifyValidator, soeREQUIRED}, diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index de3560d7f9..fc7f367562 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -703,7 +703,7 @@ JSS(write_load); // out: GetCounts #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) JSS(name); +#define TRANSACTION(tag, value, name, ...) JSS(name); #include diff --git a/src/libxrpl/protocol/Permissions.cpp b/src/libxrpl/protocol/Permissions.cpp index dbe5325a4e..35f788e9eb 100644 --- a/src/libxrpl/protocol/Permissions.cpp +++ b/src/libxrpl/protocol/Permissions.cpp @@ -29,7 +29,7 @@ Permission::Permission() #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) {value, delegatable}, +#define TRANSACTION(tag, value, name, delegatable, ...) {value, delegatable}, #include @@ -145,4 +145,4 @@ Permission::permissionToTxType(uint32_t const& value) const return static_cast(value - 1); } -} // namespace ripple \ No newline at end of file +} // namespace ripple diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 1ffce099b8..478e6a4066 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include @@ -28,6 +29,7 @@ namespace ripple { SField::IsSigning const SField::notSigning; int SField::num = 0; std::map SField::knownCodeToField; +std::map SField::knownNameToField; // Give only this translation unit permission to construct SFields struct SField::private_access_tag_t @@ -45,7 +47,7 @@ TypedField::TypedField(private_access_tag_t pat, Args&&... args) } // Construct all compile-time SFields, and register them in the knownCodeToField -// database: +// and knownNameToField databases: // Use macros for most SField construction to enforce naming conventions. #pragma push_macro("UNTYPED_SFIELD") @@ -99,7 +101,14 @@ SField::SField( , signingField(signing) , jsonName(fieldName.c_str()) { + XRPL_ASSERT( + !knownCodeToField.contains(fieldCode), + "ripple::SField::SField(tid,fv,fn,meta,signing) : fieldCode is unique"); + XRPL_ASSERT( + !knownNameToField.contains(fieldName), + "ripple::SField::SField(tid,fv,fn,meta,signing) : fieldName is unique"); knownCodeToField[fieldCode] = this; + knownNameToField[fieldName] = this; } SField::SField(private_access_tag_t, int fc) @@ -111,6 +120,9 @@ SField::SField(private_access_tag_t, int fc) , signingField(IsSigning::yes) , jsonName(fieldName.c_str()) { + XRPL_ASSERT( + !knownCodeToField.contains(fieldCode), + "ripple::SField::SField(fc) : fieldCode is unique"); knownCodeToField[fieldCode] = this; } @@ -145,11 +157,11 @@ SField::compare(SField const& f1, SField const& f2) SField const& SField::getField(std::string const& fieldName) { - for (auto const& [_, f] : knownCodeToField) + auto it = knownNameToField.find(fieldName); + + if (it != knownNameToField.end()) { - (void)_; - if (f->fieldName == fieldName) - return *f; + return *(it->second); } return sfInvalid; } diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 5edffeb666..f5e2125b3c 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -55,7 +55,7 @@ TxFormats::TxFormats() #undef TRANSACTION #define UNWRAP(...) __VA_ARGS__ -#define TRANSACTION(tag, value, name, delegatable, fields) \ +#define TRANSACTION(tag, value, name, delegatable, privileges, fields) \ add(jss::name, tag, UNWRAP fields, commonFields); #include diff --git a/src/test/app/AMMCalc_test.cpp b/src/test/app/AMMCalc_test.cpp index bebf2844b6..7349b38766 100644 --- a/src/test/app/AMMCalc_test.cpp +++ b/src/test/app/AMMCalc_test.cpp @@ -67,7 +67,7 @@ class AMMCalc_test : public beast::unit_test::suite // drops else if (match[1] == "XRPA") return XRPAmount{std::stoll(match[2])}; - return amountFromString(gw[match[1]], match[2]); + return amountFromString(gw[match[1]].asset(), match[2]); } return std::nullopt; } diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index e0b3dc1ec7..9d926fdcc1 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -7161,7 +7161,7 @@ private: using namespace test::jtx; auto const testCase = [&](std::string suffix, FeatureBitset features) { - testcase("Failed pseudo-account allocation " + suffix); + testcase("Fail pseudo-account allocation " + suffix); Env env{*this, features}; env.fund(XRP(30'000), gw, alice); env.close(); diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index fa6505e926..005ab0cc20 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -34,15 +34,6 @@ namespace ripple { namespace test { -static inline bool -checkVL( - std::shared_ptr const& sle, - SField const& field, - std::string const& expected) -{ - return strHex(expected) == strHex(sle->getFieldVL(field)); -} - struct Credentials_test : public beast::unit_test::suite { void diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index c885ed0861..94c0ced162 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -27,14 +27,6 @@ namespace ripple { namespace test { -bool -checkVL(Slice const& result, std::string expected) -{ - Serializer s; - s.addRaw(result); - return s.getString() == expected; -} - struct DID_test : public beast::unit_test::suite { void diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index 7a7511e6f0..840a6cdb43 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -37,7 +37,6 @@ #include namespace ripple { -namespace test { namespace detail { constexpr char const* realValidatorContents() @@ -56,6 +55,7 @@ auto constexpr default_expires = std::chrono::seconds{3600}; auto constexpr default_effective_overlap = std::chrono::seconds{30}; } // namespace detail +namespace test { class ValidatorSite_test : public beast::unit_test::suite { private: diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 67cc3812df..d6e1dfc73f 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -1111,7 +1111,7 @@ class Vault_test : public beast::unit_test::suite env.close(); Vault vault{env}; - Asset asset = issuer["IOU"]; + Asset asset = issuer["IOU"].asset(); auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); @@ -1131,7 +1131,7 @@ class Vault_test : public beast::unit_test::suite env.close(); Vault vault{env}; - Asset asset = issuer["IOU"]; + Asset asset = issuer["IOU"].asset(); auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); env(tx, ter(terNO_RIPPLE)); @@ -1148,7 +1148,7 @@ class Vault_test : public beast::unit_test::suite env.close(); Vault vault{env}; - Asset asset = issuer["IOU"]; + Asset asset = issuer["IOU"].asset(); { auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); @@ -2559,7 +2559,7 @@ class Vault_test : public beast::unit_test::suite { using namespace test::jtx; - testcase("failed pseudo-account allocation"); + testcase("fail pseudo-account allocation"); Env env{*this, supported_amendments() | featureSingleAssetVault}; Account const owner{"owner"}; Vault vault{env}; diff --git a/src/test/basics/FileUtilities_test.cpp b/src/test/basics/FileUtilities_test.cpp index 4b4cbe70c8..054a6bc0f7 100644 --- a/src/test/basics/FileUtilities_test.cpp +++ b/src/test/basics/FileUtilities_test.cpp @@ -31,7 +31,7 @@ public: void testGetFileContents() { - using namespace ripple::test::detail; + using namespace ripple::detail; using namespace boost::system; constexpr char const* expectedContents = diff --git a/src/test/basics/FeeUnits_test.cpp b/src/test/basics/Units_test.cpp similarity index 92% rename from src/test/basics/FeeUnits_test.cpp rename to src/test/basics/Units_test.cpp index 6266288896..d22ff018dd 100644 --- a/src/test/basics/FeeUnits_test.cpp +++ b/src/test/basics/Units_test.cpp @@ -17,13 +17,13 @@ */ #include -#include #include +#include namespace ripple { namespace test { -class feeunits_test : public beast::unit_test::suite +class units_test : public beast::unit_test::suite { private: void @@ -35,16 +35,16 @@ private: XRPAmount x{100}; BEAST_EXPECT(x.drops() == 100); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); auto y = 4u * x; BEAST_EXPECT(y.value() == 400); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); auto z = 4 * y; BEAST_EXPECT(z.value() == 1600); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); FeeLevel32 f{10}; FeeLevel32 baseFee{100}; @@ -55,7 +55,7 @@ private: BEAST_EXPECT(drops.value() == 1000); BEAST_EXPECT((std::is_same_v< std::remove_reference_t::unit_type, - feeunit::dropTag>)); + unit::dropTag>)); BEAST_EXPECT((std::is_same_v< std::remove_reference_t, @@ -65,11 +65,11 @@ private: XRPAmount x{100}; BEAST_EXPECT(x.value() == 100); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); auto y = 4u * x; BEAST_EXPECT(y.value() == 400); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); FeeLevel64 f{10}; FeeLevel64 baseFee{100}; @@ -80,7 +80,7 @@ private: BEAST_EXPECT(drops.value() == 1000); BEAST_EXPECT((std::is_same_v< std::remove_reference_t::unit_type, - feeunit::dropTag>)); + unit::dropTag>)); BEAST_EXPECT((std::is_same_v< std::remove_reference_t, XRPAmount>)); @@ -89,12 +89,12 @@ private: FeeLevel64 x{1024}; BEAST_EXPECT(x.value() == 1024); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); std::uint64_t m = 4; auto y = m * x; BEAST_EXPECT(y.value() == 4096); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); XRPAmount basefee{10}; FeeLevel64 referencefee{256}; @@ -105,7 +105,7 @@ private: BEAST_EXPECT(drops.value() == 40); BEAST_EXPECT((std::is_same_v< std::remove_reference_t::unit_type, - feeunit::dropTag>)); + unit::dropTag>)); BEAST_EXPECT((std::is_same_v< std::remove_reference_t, XRPAmount>)); @@ -181,7 +181,7 @@ private: void testFunctions() { - // Explicitly test every defined function for the TaggedFee class + // Explicitly test every defined function for the ValueUnit class // since some of them are templated, but not used anywhere else. using FeeLevel32 = FeeLevel; @@ -191,8 +191,8 @@ private: return FeeLevel64{x}; }; + [[maybe_unused]] FeeLevel64 defaulted; - (void)defaulted; FeeLevel64 test{0}; BEAST_EXPECT(test.fee() == 0); @@ -278,8 +278,8 @@ private: return FeeLevelDouble{x}; }; + [[maybe_unused]] FeeLevelDouble defaulted; - (void)defaulted; FeeLevelDouble test{0}; BEAST_EXPECT(test.fee() == 0); @@ -371,7 +371,7 @@ public: } }; -BEAST_DEFINE_TESTSUITE(feeunits, ripple_basics, ripple); +BEAST_DEFINE_TESTSUITE(units, ripple_basics, ripple); } // namespace test } // namespace ripple diff --git a/src/test/basics/base_uint_test.cpp b/src/test/basics/base_uint_test.cpp index 8058e0d6f0..a6be1f327d 100644 --- a/src/test/basics/base_uint_test.cpp +++ b/src/test/basics/base_uint_test.cpp @@ -152,6 +152,7 @@ struct base_uint_test : beast::unit_test::suite uset.insert(u); BEAST_EXPECT(raw.size() == u.size()); BEAST_EXPECT(to_string(u) == "0102030405060708090A0B0C"); + BEAST_EXPECT(to_short_string(u) == "01020304..."); BEAST_EXPECT(*u.data() == 1); BEAST_EXPECT(u.signum() == 1); BEAST_EXPECT(!!u); @@ -174,6 +175,7 @@ struct base_uint_test : beast::unit_test::suite test96 v{~u}; uset.insert(v); BEAST_EXPECT(to_string(v) == "FEFDFCFBFAF9F8F7F6F5F4F3"); + BEAST_EXPECT(to_short_string(v) == "FEFDFCFB..."); BEAST_EXPECT(*v.data() == 0xfe); BEAST_EXPECT(v.signum() == 1); BEAST_EXPECT(!!v); @@ -194,6 +196,7 @@ struct base_uint_test : beast::unit_test::suite test96 z{beast::zero}; uset.insert(z); BEAST_EXPECT(to_string(z) == "000000000000000000000000"); + BEAST_EXPECT(to_short_string(z) == "00000000..."); BEAST_EXPECT(*z.data() == 0); BEAST_EXPECT(*z.begin() == 0); BEAST_EXPECT(*std::prev(z.end(), 1) == 0); @@ -214,6 +217,7 @@ struct base_uint_test : beast::unit_test::suite BEAST_EXPECT(n == z); n--; BEAST_EXPECT(to_string(n) == "FFFFFFFFFFFFFFFFFFFFFFFF"); + BEAST_EXPECT(to_short_string(n) == "FFFFFFFF..."); n = beast::zero; BEAST_EXPECT(n == z); @@ -224,6 +228,7 @@ struct base_uint_test : beast::unit_test::suite test96 x{zm1 ^ zp1}; uset.insert(x); BEAST_EXPECTS(to_string(x) == "FFFFFFFFFFFFFFFFFFFFFFFE", to_string(x)); + BEAST_EXPECTS(to_short_string(x) == "FFFFFFFF...", to_short_string(x)); BEAST_EXPECT(uset.size() == 4); diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 8b0fce1e20..a1a6a079cc 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -128,7 +128,7 @@ backend=sqlite /** Write a rippled config file and remove when done. */ -class RippledCfgGuard : public ripple::test::detail::FileDirGuard +class RippledCfgGuard : public ripple::detail::FileDirGuard { private: path dataDir_; @@ -239,7 +239,7 @@ moreripplevalidators.net /** Write a validators.txt file and remove when done. */ -class ValidatorsTxtGuard : public test::detail::FileDirGuard +class ValidatorsTxtGuard : public detail::FileDirGuard { public: ValidatorsTxtGuard( @@ -345,7 +345,7 @@ port_wss_admin { // read from file absolute path auto const cwd = current_path(); - ripple::test::detail::DirGuard const g0(*this, "test_db"); + ripple::detail::DirGuard const g0(*this, "test_db"); path const dataDirRel("test_data_dir"); path const dataDirAbs(cwd / g0.subdir() / dataDirRel); detail::RippledCfgGuard const g( diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 3f079eac17..e65a7af913 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -30,9 +30,6 @@ #include namespace ripple { -namespace test { -namespace csf { - namespace detail { // Dummy class when no edge data needed for graph struct NoEdgeData @@ -41,6 +38,9 @@ struct NoEdgeData } // namespace detail +namespace test { +namespace csf { + /** Directed graph Basic directed graph that uses an adjacency list to represent out edges. diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index de6b83362d..3a2171a420 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -469,9 +469,15 @@ public: Returns 0 if the trust line does not exist. */ // VFALCO NOTE This should return a unit-less amount + PrettyAmount + balance(Account const& account, Asset const& asset) const; + PrettyAmount balance(Account const& account, Issue const& issue) const; + PrettyAmount + balance(Account const& account, MPTIssue const& mptIssue) const; + /** Return the number of objects owned by an account. * Returns 0 if the account does not exist. */ diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 534419494d..11c13543e4 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -26,7 +26,9 @@ #include #include #include +#include #include +#include #include #include @@ -35,6 +37,252 @@ namespace ripple { namespace test { namespace jtx { +/** Generic helper class for helper clases that set a field on a JTx. + + Not every helper will be able to use this because of conversions and other + issues, but for classes where it's straightforward, this can simplify things. +*/ +template < + class SField, + class StoredValue = typename SField::type::value_type, + class OutputValue = StoredValue> +struct JTxField +{ + using SF = SField; + using SV = StoredValue; + using OV = OutputValue; + +protected: + SF const& sfield_; + SV value_; + +public: + explicit JTxField(SF const& sfield, SV const& value) + : sfield_(sfield), value_(value) + { + } + + virtual ~JTxField() = default; + + virtual OV + value() const = 0; + + virtual void + operator()(Env&, JTx& jt) const + { + jt.jv[sfield_.jsonName] = value(); + } +}; + +template +struct JTxField +{ + using SF = SField; + using SV = StoredValue; + using OV = SV; + +protected: + SF const& sfield_; + SV value_; + +public: + explicit JTxField(SF const& sfield, SV const& value) + : sfield_(sfield), value_(value) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfield_.jsonName] = value_; + } +}; + +struct timePointField + : public JTxField +{ + using SF = SF_UINT32; + using SV = NetClock::time_point; + using OV = NetClock::rep; + using base = JTxField; + +protected: + using base::value_; + +public: + explicit timePointField(SF const& sfield, SV const& value) + : JTxField(sfield, value) + { + } + + OV + value() const override + { + return value_.time_since_epoch().count(); + } +}; + +struct uint256Field : public JTxField +{ + using SF = SF_UINT256; + using SV = uint256; + using OV = std::string; + using base = JTxField; + +protected: + using base::value_; + +public: + explicit uint256Field(SF const& sfield, SV const& value) + : JTxField(sfield, value) + { + } + + OV + value() const override + { + return to_string(value_); + } +}; + +struct accountIDField : public JTxField +{ + using SF = SF_ACCOUNT; + using SV = AccountID; + using OV = std::string; + using base = JTxField; + +protected: + using base::value_; + +public: + explicit accountIDField(SF const& sfield, SV const& value) + : JTxField(sfield, value) + { + } + + OV + value() const override + { + return toBase58(value_); + } +}; + +struct blobField : public JTxField +{ + using SF = SF_VL; + using SV = std::string; + using base = JTxField; + + using JTxField::JTxField; + + explicit blobField(SF const& sfield, Slice const& cond) + : JTxField(sfield, strHex(cond)) + { + } + + template + explicit blobField(SF const& sfield, std::array const& c) + : blobField(sfield, makeSlice(c)) + { + } +}; + +template +struct valueUnitField + : public JTxField, ValueType> +{ + using SF = SField; + using SV = unit::ValueUnit; + using OV = ValueType; + using base = JTxField; + + static_assert(std::is_same_v); + +protected: + using base::value_; + +public: + using JTxField::JTxField; + + OV + value() const override + { + return value_.value(); + } +}; + +template +struct JTxFieldWrapper +{ + using JF = JTxField; + using SF = typename JF::SF; + using SV = typename JF::SV; + +protected: + SF const& sfield_; + +public: + explicit JTxFieldWrapper(SF const& sfield) : sfield_(sfield) + { + } + + JF + operator()(SV const& value) const + { + return JTxField(sfield_, value); + } +}; + +template <> +struct JTxFieldWrapper +{ + using JF = blobField; + using SF = JF::SF; + using SV = JF::SV; + +protected: + SF const& sfield_; + +public: + explicit JTxFieldWrapper(SF const& sfield) : sfield_(sfield) + { + } + + JF + operator()(SV const& cond) const + { + return JF(sfield_, makeSlice(cond)); + } + + JF + operator()(Slice const& cond) const + { + return JF(sfield_, cond); + } + + template + JF + operator()(std::array const& c) const + { + return operator()(makeSlice(c)); + } +}; + +template < + class SField, + class UnitTag, + class ValueType = typename SField::type::value_type> +using valueUnitWrapper = + JTxFieldWrapper>; + +template +using simpleField = JTxFieldWrapper>; + +/** General field definitions, or fields used in multiple transaction namespaces + */ +auto const data = JTxFieldWrapper(sfData); + // TODO We only need this long "requires" clause as polyfill, for C++20 // implementations which are missing header. Replace with // `std::ranges::range`, and accordingly use std::ranges::begin/end @@ -102,6 +350,25 @@ checkArraySize(Json::Value const& val, unsigned int size); std::uint32_t ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); +[[nodiscard]] +inline bool +checkVL(Slice const& result, std::string expected) +{ + Serializer s; + s.addRaw(result); + return s.getString() == expected; +} + +[[nodiscard]] +inline bool +checkVL( + std::shared_ptr const& sle, + SField const& field, + std::string const& expected) +{ + return strHex(expected) == strHex(sle->getFieldVL(field)); +} + /* Path finding */ /******************************************************************************/ void @@ -264,86 +531,14 @@ std::array constexpr cb1 = { std::array const fb1 = {{0xA0, 0x02, 0x80, 0x00}}; /** Set the "FinishAfter" time tag on a JTx */ -struct finish_time -{ -private: - NetClock::time_point value_; - -public: - explicit finish_time(NetClock::time_point const& value) : value_(value) - { - } - - void - operator()(Env&, JTx& jt) const - { - jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count(); - } -}; +auto const finish_time = JTxFieldWrapper(sfFinishAfter); /** Set the "CancelAfter" time tag on a JTx */ -struct cancel_time -{ -private: - NetClock::time_point value_; +auto const cancel_time = JTxFieldWrapper(sfCancelAfter); -public: - explicit cancel_time(NetClock::time_point const& value) : value_(value) - { - } +auto const condition = JTxFieldWrapper(sfCondition); - void - operator()(jtx::Env&, jtx::JTx& jt) const - { - jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count(); - } -}; - -struct condition -{ -private: - std::string value_; - -public: - explicit condition(Slice const& cond) : value_(strHex(cond)) - { - } - - template - explicit condition(std::array const& c) - : condition(makeSlice(c)) - { - } - - void - operator()(Env&, JTx& jt) const - { - jt.jv[sfCondition.jsonName] = value_; - } -}; - -struct fulfillment -{ -private: - std::string value_; - -public: - explicit fulfillment(Slice condition) : value_(strHex(condition)) - { - } - - template - explicit fulfillment(std::array f) - : fulfillment(makeSlice(f)) - { - } - - void - operator()(Env&, JTx& jt) const - { - jt.jv[sfFulfillment.jsonName] = value_; - } -}; +auto const fulfillment = JTxFieldWrapper(sfFulfillment); /* Payment Channel */ /******************************************************************************/ diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index 344a2ab73c..d0b21d31ce 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -24,9 +24,9 @@ #include #include -#include #include #include +#include #include #include @@ -34,6 +34,16 @@ #include namespace ripple { + +namespace detail { + +struct epsilon_multiple +{ + std::size_t n; +}; + +} // namespace detail + namespace test { namespace jtx { @@ -57,7 +67,7 @@ struct AnyAmount; // struct None { - Issue issue; + Asset asset; }; //------------------------------------------------------------------------------ @@ -133,6 +143,12 @@ public: return amount_; } + inline int + signum() const + { + return amount_.signum(); + } + operator STAmount const&() const { return amount_; @@ -165,17 +181,17 @@ struct PrettyAsset { private: Asset asset_; - unsigned int scale_; + std::uint64_t scale_; public: template requires std::convertible_to - PrettyAsset(A const& asset, unsigned int scale = 1) + PrettyAsset(A const& asset, std::uint32_t scale = 1) : PrettyAsset{Asset{asset}, scale} { } - PrettyAsset(Asset const& asset, unsigned int scale = 1) + PrettyAsset(Asset const& asset, std::uint32_t scale = 1) : asset_(asset), scale_(scale) { } @@ -199,10 +215,22 @@ public: template PrettyAmount operator()(T v) const + { + return operator()(Number(v)); + } + + PrettyAmount + operator()(Number v) const { STAmount amount{asset_, v * scale_}; return {amount, ""}; } + + None + operator()(none_t) const + { + return {asset_}; + } }; //------------------------------------------------------------------------------ @@ -312,15 +340,6 @@ drops(XRPAmount i) //------------------------------------------------------------------------------ -namespace detail { - -struct epsilon_multiple -{ - std::size_t n; -}; - -} // namespace detail - // The smallest possible IOU STAmount struct epsilon_t { @@ -360,6 +379,11 @@ public: { return {currency, account.id()}; } + Asset + asset() const + { + return issue(); + } /** Implicit conversion to Issue or Asset. @@ -370,9 +394,9 @@ public: { return issue(); } - operator Asset() const + operator PrettyAsset() const { - return issue(); + return asset(); } template < @@ -438,14 +462,32 @@ public: return issuanceID; } - /** Implicit conversion to MPTIssue. + /** Explicit conversion to MPTIssue or asset. + */ + ripple::MPTIssue + mptIssue() const + { + return MPTIssue{issuanceID}; + } + Asset + asset() const + { + return mptIssue(); + } + + /** Implicit conversion to MPTIssue or asset. This allows passing an MPT value where an MPTIssue is expected. */ operator ripple::MPTIssue() const { - return MPTIssue{issuanceID}; + return mptIssue(); + } + + operator PrettyAsset() const + { + return asset(); } template @@ -461,6 +503,13 @@ public: PrettyAmount operator()(detail::epsilon_multiple) const; + /** Returns None-of-Issue */ + None + operator()(none_t) const + { + return {mptIssue()}; + } + friend BookSpec operator~(MPT const& mpt) { diff --git a/src/test/jtx/balance.h b/src/test/jtx/balance.h index 3a2cf0423f..0c4a6cca1d 100644 --- a/src/test/jtx/balance.h +++ b/src/test/jtx/balance.h @@ -38,9 +38,9 @@ namespace jtx { class balance { private: - bool none_; - Account account_; - STAmount value_; + bool const none_; + Account const account_; + STAmount const value_; public: balance(Account const& account, none_t) @@ -49,7 +49,7 @@ public: } balance(Account const& account, None const& value) - : none_(true), account_(account), value_(value.issue) + : none_(true), account_(account), value_(value.asset) { } diff --git a/src/test/jtx/fee.h b/src/test/jtx/fee.h index 7d54804f87..3e3740b80d 100644 --- a/src/test/jtx/fee.h +++ b/src/test/jtx/fee.h @@ -37,6 +37,7 @@ class fee { private: bool manual_ = true; + bool increment_ = false; std::optional amount_; public: @@ -44,6 +45,10 @@ public: { } + explicit fee(increment_t) : increment_(true) + { + } + explicit fee(none_t) { } diff --git a/src/test/jtx/flags.h b/src/test/jtx/flags.h index 09e5dac52f..4adc75c6a8 100644 --- a/src/test/jtx/flags.h +++ b/src/test/jtx/flags.h @@ -27,22 +27,6 @@ #include namespace ripple { -namespace test { -namespace jtx { - -// JSON generators - -/** Add and/or remove flag. */ -Json::Value -fset(Account const& account, std::uint32_t on, std::uint32_t off = 0); - -/** Remove account flag. */ -inline Json::Value -fclear(Account const& account, std::uint32_t off) -{ - return fset(account, 0, off); -} - namespace detail { class flags_helper @@ -120,6 +104,22 @@ protected: } // namespace detail +namespace test { +namespace jtx { + +// JSON generators + +/** Add and/or remove flag. */ +Json::Value +fset(Account const& account, std::uint32_t on, std::uint32_t off = 0); + +/** Remove account flag. */ +inline Json::Value +fclear(Account const& account, std::uint32_t off) +{ + return fset(account, 0, off); +} + /** Match set account flags */ class flags : private detail::flags_helper { diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index ac00d3eed1..96b63bd927 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -199,6 +199,26 @@ Env::balance(Account const& account, Issue const& issue) const return {amount, lookup(issue.account).name()}; } +PrettyAmount +Env::balance(Account const& account, MPTIssue const& mptIssue) const +{ + auto const sle = le(keylet::mptoken(mptIssue.getMptID(), account)); + if (!sle) + { + return {STAmount(mptIssue, 0), account.name()}; + } + STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)}; + return {amount, lookup(mptIssue.getIssuer()).name()}; +} + +PrettyAmount +Env::balance(Account const& account, Asset const& asset) const +{ + return std::visit( + [&](auto const& issue) { return balance(account, issue); }, + asset.value()); +} + std::uint32_t Env::ownerCount(Account const& account) const { diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index e5b136e9c0..f5f0368d47 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -136,10 +136,30 @@ expectLine( return false; } +[[nodiscard]] bool +expectLine(Env& env, AccountID const& account, None const&, Issue const& issue) +{ + return !env.le(keylet::line(account, issue)); +} + +[[nodiscard]] bool +expectLine( + Env& env, + AccountID const& account, + None const&, + MPTIssue const& mptIssue) +{ + return !env.le(keylet::mptoken(mptIssue.getMptID(), account)); +} + [[nodiscard]] bool expectLine(Env& env, AccountID const& account, None const& value) { - return !env.le(keylet::line(account, value.issue)); + return std::visit( + [&](auto const& issue) { + return expectLine(env, account, value, issue); + }, + value.asset.value()); } [[nodiscard]] bool diff --git a/src/test/jtx/impl/balance.cpp b/src/test/jtx/impl/balance.cpp index 42330658eb..decbd816e1 100644 --- a/src/test/jtx/impl/balance.cpp +++ b/src/test/jtx/impl/balance.cpp @@ -24,38 +24,73 @@ namespace test { namespace jtx { void -balance::operator()(Env& env) const +doBalance( + Env& env, + AccountID const& account, + bool none, + STAmount const& value, + Issue const& issue) { - if (isXRP(value_.issue())) + if (isXRP(issue)) { - auto const sle = env.le(account_); - if (none_) + auto const sle = env.le(keylet::account(account)); + if (none) { env.test.expect(!sle); } else if (env.test.expect(sle)) { - env.test.expect(sle->getFieldAmount(sfBalance) == value_); + env.test.expect(sle->getFieldAmount(sfBalance) == value); } } else { - auto const sle = env.le(keylet::line(account_.id(), value_.issue())); - if (none_) + auto const sle = env.le(keylet::line(account, issue)); + if (none) { env.test.expect(!sle); } else if (env.test.expect(sle)) { auto amount = sle->getFieldAmount(sfBalance); - amount.setIssuer(value_.issue().account); - if (account_.id() > value_.issue().account) + amount.setIssuer(issue.account); + if (account > issue.account) amount.negate(); - env.test.expect(amount == value_); + env.test.expect(amount == value); } } } +void +doBalance( + Env& env, + AccountID const& account, + bool none, + STAmount const& value, + MPTIssue const& mptIssue) +{ + auto const sle = env.le(keylet::mptoken(mptIssue.getMptID(), account)); + if (none) + { + env.test.expect(!sle); + } + else if (env.test.expect(sle)) + { + STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)}; + env.test.expect(amount == value); + } +} + +void +balance::operator()(Env& env) const +{ + return std::visit( + [&](auto const& issue) { + doBalance(env, account_.id(), none_, value_, issue); + }, + value_.asset().value()); +} + } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/impl/fee.cpp b/src/test/jtx/impl/fee.cpp index 71e3dd089a..b887849946 100644 --- a/src/test/jtx/impl/fee.cpp +++ b/src/test/jtx/impl/fee.cpp @@ -26,13 +26,15 @@ namespace test { namespace jtx { void -fee::operator()(Env&, JTx& jt) const +fee::operator()(Env& env, JTx& jt) const { if (!manual_) return; jt.fill_fee = false; + if (increment_) + jt[sfFee] = STAmount(env.current()->fees().increment).getJson(); if (amount_) - jt[jss::Fee] = amount_->getJson(JsonOptions::none); + jt[sfFee] = amount_->getJson(JsonOptions::none); } } // namespace jtx diff --git a/src/test/jtx/impl/owners.cpp b/src/test/jtx/impl/owners.cpp index 386ec29a37..b55986fccb 100644 --- a/src/test/jtx/impl/owners.cpp +++ b/src/test/jtx/impl/owners.cpp @@ -20,9 +20,6 @@ #include namespace ripple { -namespace test { -namespace jtx { - namespace detail { std::uint32_t @@ -39,7 +36,7 @@ owned_count_of(ReadView const& view, AccountID const& id, LedgerEntryType type) void owned_count_helper( - Env& env, + test::jtx::Env& env, AccountID const& id, LedgerEntryType type, std::uint32_t value) @@ -49,6 +46,9 @@ owned_count_helper( } // namespace detail +namespace test { +namespace jtx { + void owners::operator()(Env& env) const { diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index fc904f9e87..9408d67a9c 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -30,8 +30,6 @@ #include namespace ripple { -namespace test { -namespace jtx { namespace detail { @@ -40,13 +38,16 @@ owned_count_of(ReadView const& view, AccountID const& id, LedgerEntryType type); void owned_count_helper( - Env& env, + test::jtx::Env& env, AccountID const& id, LedgerEntryType type, std::uint32_t value); } // namespace detail +namespace test { +namespace jtx { + // Helper for aliases template class owner_count diff --git a/src/test/jtx/require.h b/src/test/jtx/require.h index bec21235a6..3215ac0abb 100644 --- a/src/test/jtx/require.h +++ b/src/test/jtx/require.h @@ -26,14 +26,12 @@ #include namespace ripple { -namespace test { -namespace jtx { namespace detail { template inline void -require_args(requires_t& vec, Cond const& cond, Args const&... args) +require_args(test::jtx::requires_t& vec, Cond const& cond, Args const&... args) { vec.push_back(cond); if constexpr (sizeof...(args) > 0) @@ -42,6 +40,9 @@ require_args(requires_t& vec, Cond const& cond, Args const&... args) } // namespace detail +namespace test { +namespace jtx { + /** Compose many condition functors into one */ template require_t diff --git a/src/test/jtx/tags.h b/src/test/jtx/tags.h index bb64295f05..4d55929d69 100644 --- a/src/test/jtx/tags.h +++ b/src/test/jtx/tags.h @@ -49,6 +49,16 @@ struct disabled_t }; static disabled_t const disabled; +/** Used for fee() calls that use an owner reserve increment */ +struct increment_t +{ + increment_t() + { + } +}; + +static increment_t const increment; + } // namespace jtx } // namespace test diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 7ceb76504d..5bb9feb070 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -31,6 +31,7 @@ #include namespace ripple { +namespace test { class Invariants_test : public beast::unit_test::suite { @@ -112,13 +113,13 @@ class Invariants_test : public beast::unit_test::suite { terActual = ac.checkInvariants(terActual, fee); BEAST_EXPECT(terExpect == terActual); + auto const messages = sink.messages().str(); BEAST_EXPECT( - sink.messages().str().starts_with("Invariant failed:") || - sink.messages().str().starts_with( - "Transaction caused an exception")); + messages.starts_with("Invariant failed:") || + messages.starts_with("Transaction caused an exception")); for (auto const& m : expect_logs) { - if (sink.messages().str().find(m) == std::string::npos) + if (messages.find(m) == std::string::npos) { // uncomment if you want to log the invariant failure // message log << " --> " << m << std::endl; @@ -1300,6 +1301,118 @@ class Invariants_test : public beast::unit_test::suite {tecINVARIANT_FAILED, tecINVARIANT_FAILED}); } + void + testValidPseudoAccounts() + { + testcase << "valid pseudo accounts"; + + using namespace jtx; + + AccountID pseudoAccountID; + Preclose createPseudo = + [&, this](Account const& a, Account const& b, Env& env) { + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + + // Create vault + Vault vault{env}; + auto [tx, vKeylet] = + vault.create({.owner = a, .asset = xrpAsset}); + env(tx); + env.close(); + if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle)) + { + pseudoAccountID = vSle->at(sfAccount); + } + + return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID))); + }; + + /* Cases to check + "pseudo-account has 0 pseudo-account fields set" + "pseudo-account has 2 pseudo-account fields set" + "pseudo-account sequence changed" + "pseudo-account flags are not set" + "pseudo-account has a regular key" + */ + struct Mod + { + std::string expectedFailure; + std::function func; + }; + auto const mods = std::to_array({ + { + "pseudo-account has 0 pseudo-account fields set", + [this](SLE::pointer& sle) { + BEAST_EXPECT(sle->at(~sfVaultID)); + sle->at(~sfVaultID) = std::nullopt; + }, + }, + { + "pseudo-account has 2 pseudo-account fields set", + [this](SLE::pointer& sle) { + BEAST_EXPECT(sle->at(~sfVaultID) && !sle->at(~sfAMMID)); + sle->at(~sfAMMID) = ~sle->at(~sfVaultID); + }, + }, + /* + { + "pseudo-account has 2 pseudo-account fields set", + [this](SLE::pointer& sle) { + BEAST_EXPECT( + sle->at(~sfVaultID) && !sle->at(~sfLoanBrokerID)); + sle->at(~sfLoanBrokerID) = ~sle->at(~sfVaultID); + }, + }, + */ + { + "pseudo-account sequence changed", + [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; }, + }, + { + "pseudo-account flags are not set", + [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; }, + }, + { + "pseudo-account has a regular key", + [](SLE::pointer& sle) { + sle->at(sfRegularKey) = Account("regular").id(); + }, + }, + }); + + for (auto const& mod : mods) + { + doInvariantCheck( + {{mod.expectedFailure}}, + [&](Account const& A1, Account const&, ApplyContext& ac) { + auto sle = ac.view().peek(keylet::account(pseudoAccountID)); + if (!sle) + return false; + mod.func(sle); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + createPseudo); + } + + // Take one of the regular accounts and set the sequence to 0, which + // will make it look like a pseudo-account + doInvariantCheck( + {{"pseudo-account has 0 pseudo-account fields set"}, + {"pseudo-account sequence changed"}, + {"pseudo-account flags are not set"}}, + [&](Account const& A1, Account const&, ApplyContext& ac) { + auto sle = ac.view().peek(keylet::account(A1.id())); + if (!sle) + return false; + sle->at(sfSequence) = 0; + ac.view().update(sle); + return true; + }); + } public: void run() override @@ -1318,9 +1431,11 @@ public: testValidNewAccountRoot(); testNFTokenPageInvariants(); testPermissionedDomainInvariants(); + testValidPseudoAccounts(); } }; BEAST_DEFINE_TESTSUITE(Invariants, ledger, ripple); +} // namespace test } // namespace ripple diff --git a/src/test/nodestore/import_test.cpp b/src/test/nodestore/import_test.cpp index d7865a20fc..fd52d07c4c 100644 --- a/src/test/nodestore/import_test.cpp +++ b/src/test/nodestore/import_test.cpp @@ -61,7 +61,6 @@ multi(32gb): */ namespace ripple { -namespace NodeStore { namespace detail { @@ -191,6 +190,8 @@ fmtdur(std::chrono::duration const& d) } // namespace detail +namespace NodeStore { + //------------------------------------------------------------------------------ class progress diff --git a/src/test/unit_test/FileDirGuard.h b/src/test/unit_test/FileDirGuard.h index d247ae3015..e7d5e1ab96 100644 --- a/src/test/unit_test/FileDirGuard.h +++ b/src/test/unit_test/FileDirGuard.h @@ -27,7 +27,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include namespace ripple { -namespace test { + namespace detail { /** @@ -176,7 +176,6 @@ public: }; } // namespace detail -} // namespace test } // namespace ripple #endif // TEST_UNIT_TEST_DIRGUARD_H diff --git a/src/test/unit_test/multi_runner.cpp b/src/test/unit_test/multi_runner.cpp index 087e37dac2..3755428ee3 100644 --- a/src/test/unit_test/multi_runner.cpp +++ b/src/test/unit_test/multi_runner.cpp @@ -30,7 +30,6 @@ #include namespace ripple { -namespace test { namespace detail { @@ -388,6 +387,8 @@ multi_runner_base::add_failures(std::size_t failures) } // namespace detail +namespace test { + //------------------------------------------------------------------------------ multi_runner_parent::multi_runner_parent() : os_(std::cout) @@ -645,10 +646,11 @@ multi_runner_child::on_log(std::string const& msg) message_queue_send(MessageType::log, s.str()); } +} // namespace test + namespace detail { template class multi_runner_base; template class multi_runner_base; } // namespace detail -} // namespace test } // namespace ripple diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index 08512d1882..bce62fb131 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -40,7 +40,6 @@ #include namespace ripple { -namespace test { namespace detail { @@ -212,6 +211,8 @@ public: } // namespace detail +namespace test { + //------------------------------------------------------------------------------ /** Manager for children running unit tests diff --git a/src/xrpld/app/misc/detail/LoadFeeTrack.cpp b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp index 96e7555401..90c084c44b 100644 --- a/src/xrpld/app/misc/detail/LoadFeeTrack.cpp +++ b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include diff --git a/src/xrpld/app/tx/detail/DeleteAccount.cpp b/src/xrpld/app/tx/detail/DeleteAccount.cpp index 7aa47e05f3..a2a9769d43 100644 --- a/src/xrpld/app/tx/detail/DeleteAccount.cpp +++ b/src/xrpld/app/tx/detail/DeleteAccount.cpp @@ -31,10 +31,10 @@ #include #include #include -#include #include #include #include +#include namespace ripple { diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index aa1464ec2a..1a707e8496 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -26,14 +26,67 @@ #include #include -#include #include +#include #include #include +#include #include namespace ripple { +enum Privilege { + noPriv = + 0x0000, // The transaction can not do any of the enumerated operations + createAcct = + 0x0001, // The transaction can create a new ACCOUNT_ROOT object. + createPseudoAcct = 0x0002, // The transaction can create a pseudo account, + // which implies createAcct + mustDeleteAcct = + 0x0004, // The transaction must delete an ACCOUNT_ROOT object + mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT + // object, but does not have to + overrideFreeze = 0x0010, // The transaction can override some freeze rules + changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT + createMPTIssuance = + 0x0040, // The transaction can create a new MPT issuance + destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance + mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT + // object (except by issuer) + mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT + // object (except by issuer) +}; +constexpr Privilege +operator|(Privilege lhs, Privilege rhs) +{ + return safe_cast( + safe_cast>(lhs) | + safe_cast>(rhs)); +} + +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, delegatable, privileges, ...) \ + case tag: { \ + return (privileges) & priv; \ + } + +bool +checkMyPrivilege(STTx const& tx, Privilege priv) +{ + switch (tx.getTxnType()) + { +#include + // Deprecated types + default: + return false; + } +}; + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + void TransactionFeeCheck::visitEntry( bool, @@ -328,10 +381,7 @@ AccountRootsNotDeleted::finalize( // transaction when the total AMM LP Tokens balance goes to 0. // A successful AccountDelete or AMMDelete MUST delete exactly // one account root. - if ((tx.getTxnType() == ttACCOUNT_DELETE || - tx.getTxnType() == ttAMM_DELETE || - tx.getTxnType() == ttVAULT_DELETE) && - result == tesSUCCESS) + if (checkMyPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS) { if (accountsDeleted_ == 1) return true; @@ -348,9 +398,8 @@ AccountRootsNotDeleted::finalize( // A successful AMMWithdraw/AMMClawback MAY delete one account root // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw // deletes the AMM account, accountsDeleted_ is set if it is deleted. - if ((tx.getTxnType() == ttAMM_WITHDRAW || - tx.getTxnType() == ttAMM_CLAWBACK) && - result == tesSUCCESS && accountsDeleted_ == 1) + if (checkMyPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS && + accountsDeleted_ == 1) return true; if (accountsDeleted_ == 0) @@ -439,10 +488,14 @@ AccountRootsDeletedClean::finalize( } // Keys directly stored in the AccountRoot object - if (auto const ammKey = accountSLE->at(~sfAMMID)) + for (auto const& field : getPseudoAccountFields()) { - if (objectExists(keylet::amm(*ammKey)) && enforce) - return false; + if (accountSLE->isFieldPresent(*field)) + { + auto const key = accountSLE->getFieldH256(*field); + if (objectExists(keylet::unchecked(key)) && enforce) + return false; + } } } @@ -462,41 +515,23 @@ LedgerEntryTypesMatch::visitEntry( if (after) { +#pragma push_macro("LEDGER_ENTRY") +#undef LEDGER_ENTRY + +#define LEDGER_ENTRY(tag, value, name, rpcName, fields) case tag: + switch (after->getType()) { - case ltACCOUNT_ROOT: - case ltDELEGATE: - case ltDIR_NODE: - case ltRIPPLE_STATE: - case ltTICKET: - case ltSIGNER_LIST: - case ltOFFER: - case ltLEDGER_HASHES: - case ltAMENDMENTS: - case ltFEE_SETTINGS: - case ltESCROW: - case ltPAYCHAN: - case ltCHECK: - case ltDEPOSIT_PREAUTH: - case ltNEGATIVE_UNL: - case ltNFTOKEN_PAGE: - case ltNFTOKEN_OFFER: - case ltAMM: - case ltBRIDGE: - case ltXCHAIN_OWNED_CLAIM_ID: - case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: - case ltDID: - case ltORACLE: - case ltMPTOKEN_ISSUANCE: - case ltMPTOKEN: - case ltCREDENTIAL: - case ltPERMISSIONED_DOMAIN: - case ltVAULT: - break; +#include + + break; default: invalidTypeAdded_ = true; break; } + +#undef LEDGER_ENTRY +#pragma pop_macro("LEDGER_ENTRY") } } @@ -850,7 +885,7 @@ TransfersNotFrozen::validateFrozenState( } // AMMClawbacks are allowed to override some freeze rules - if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK) + if ((!isAMMLine || globalFreeze) && checkMyPrivilege(tx, overrideFreeze)) { JLOG(j.debug()) << "Invariant check allowing funds to be moved " << (change.balanceChangeSign > 0 ? "to" : "from") @@ -910,17 +945,13 @@ ValidNewAccountRoot::finalize( } // From this point on we know exactly one account was created. - if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE || - tx.getTxnType() == ttVAULT_CREATE || - tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION || - tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) && + if (checkMyPrivilege(tx, createAcct | createPseudoAcct) && result == tesSUCCESS) { bool const pseudoAccount = (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault)); - if (pseudoAccount && tx.getTxnType() != ttAMM_CREATE && - tx.getTxnType() != ttVAULT_CREATE) + if (pseudoAccount && !checkMyPrivilege(tx, createPseudoAcct)) { JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a " "wrong transaction type"; @@ -959,7 +990,7 @@ ValidNewAccountRoot::finalize( JLOG(j.fatal()) << "Invariant failed: account root created illegally"; return false; -} +} // namespace ripple //------------------------------------------------------------------------------ @@ -1154,8 +1185,7 @@ NFTokenCountTracking::finalize( ReadView const& view, beast::Journal const& j) { - if (TxType const txType = tx.getTxnType(); - txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN) + if (!checkMyPrivilege(tx, changeNFTCounts)) { if (beforeMintedTotal != afterMintedTotal) { @@ -1345,8 +1375,7 @@ ValidMPTIssuance::finalize( { if (result == tesSUCCESS) { - if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE || - tx.getTxnType() == ttVAULT_CREATE) + if (checkMyPrivilege(tx, createMPTIssuance)) { if (mptIssuancesCreated_ == 0) { @@ -1367,8 +1396,7 @@ ValidMPTIssuance::finalize( return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0; } - if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY || - tx.getTxnType() == ttVAULT_DELETE) + if (checkMyPrivilege(tx, destroyMPTIssuance)) { if (mptIssuancesDeleted_ == 0) { @@ -1389,8 +1417,7 @@ ValidMPTIssuance::finalize( return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1; } - if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE || - tx.getTxnType() == ttVAULT_DEPOSIT) + if (checkMyPrivilege(tx, mustAuthorizeMPT | mayAuthorizeMPT)) { bool const submittedByIssuer = tx.isFieldPresent(sfHolder); @@ -1416,7 +1443,7 @@ ValidMPTIssuance::finalize( return false; } else if ( - !submittedByIssuer && (tx.getTxnType() != ttVAULT_DEPOSIT) && + !submittedByIssuer && !checkMyPrivilege(tx, mayAuthorizeMPT) && (mptokensCreated_ + mptokensDeleted_ != 1)) { // if the holder submitted this tx, then a mptoken must be @@ -1580,4 +1607,98 @@ ValidPermissionedDomain::finalize( (sleStatus_[1] ? check(*sleStatus_[1], j) : true); } +//------------------------------------------------------------------------------ + +void +ValidPseudoAccounts::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete) + // Deletion is ignored + return; + + if (after && after->getType() == ltACCOUNT_ROOT) + { + bool const isPseudo = [&]() { + // isPseudoAccount checks that any of the pseudo-account fields are + // set. + if (isPseudoAccount(after)) + return true; + // Not all pseudo-accounts have a zero sequence, but all accounts + // with a zero sequence had better be pseudo-accounts. + if (after->at(sfSequence) == 0) + return true; + + return false; + }(); + if (isPseudo) + { + // Pseudo accounts must have the following properties: + // 1. Exactly one of the pseudo-account fields is set. + // 2. The sequence number is not changed. + // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth + // flags are set. + // 4. The RegularKey is not set. + { + std::vector const& fields = + getPseudoAccountFields(); + + auto const numFields = std::count_if( + fields.begin(), + fields.end(), + [&after](SField const* sf) -> bool { + return after->isFieldPresent(*sf); + }); + if (numFields != 1) + { + std::stringstream error; + error << "pseudo-account has " << numFields + << " pseudo-account fields set"; + errors_.emplace_back(error.str()); + } + } + if (before && before->at(sfSequence) != after->at(sfSequence)) + { + errors_.emplace_back("pseudo-account sequence changed"); + } + if (!after->isFlag( + lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth)) + { + errors_.emplace_back("pseudo-account flags are not set"); + } + if (after->isFieldPresent(sfRegularKey)) + { + errors_.emplace_back("pseudo-account has a regular key"); + } + } + } +} + +bool +ValidPseudoAccounts::finalize( + STTx const& tx, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + bool const enforce = view.rules().enabled(featureSingleAssetVault); + XRPL_ASSERT( + errors_.empty() || enforce, + "ripple::ValidPseudoAccounts::finalize : no bad " + "changes or enforce invariant"); + if (!errors_.empty()) + { + for (auto const& error : errors_) + { + JLOG(j.fatal()) << "Invariant failed: " << error; + } + if (enforce) + return false; + } + return true; +} + } // namespace ripple diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index 6819780114..58dbc23066 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -618,6 +618,34 @@ public: beast::Journal const&); }; +/** + * @brief Invariants: Pseudo-accounts have valid and consisent properties + * + * Pseudo-accounts have certain properties, and some of those properties are + * unique to pseudo-accounts. Check that all pseudo-accounts are following the + * rules, and that only pseudo-accounts look like pseudo-accounts. + * + */ +class ValidPseudoAccounts +{ + std::vector errors_; + +public: + void + visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&); + + bool + finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const&); +}; + // additional invariant checks can be declared above and then added to this // tuple using InvariantChecks = std::tuple< @@ -637,7 +665,8 @@ using InvariantChecks = std::tuple< NFTokenCountTracking, ValidClawback, ValidMPTIssuance, - ValidPermissionedDomain>; + ValidPermissionedDomain, + ValidPseudoAccounts>; /** * @brief get a tuple of all invariant checks diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index baba7d131e..56d3302f46 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -196,7 +196,10 @@ PreflightContext::PreflightContext( //------------------------------------------------------------------------------ Transactor::Transactor(ApplyContext& ctx) - : ctx_(ctx), j_(ctx.journal), account_(ctx.tx.getAccountID(sfAccount)) + : ctx_(ctx) + , sink_(ctx.journal, to_short_string(ctx.tx.getTransactionID()) + " ") + , j_(sink_) + , account_(ctx.tx.getAccountID(sfAccount)) { } diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index 4956f021df..88ccdb8db7 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -88,6 +89,7 @@ class Transactor { protected: ApplyContext& ctx_; + beast::WrappedSink sink_; beast::Journal const j_; AccountID const account_; diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp index 5e8c125e83..32745f703d 100644 --- a/src/xrpld/app/tx/detail/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -18,56 +18,20 @@ //============================================================================== #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +// Do nothing +#define TRANSACTION(...) +#define TRANSACTION_INCLUDE 1 + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + +// DO NOT INCLUDE TRANSACTOR HEADER FILES HERE. +// See the instructions at the top of transactions.macro instead. #include @@ -96,8 +60,8 @@ with_txn_type(TxType txnType, F&& f) #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, fields) \ - case tag: \ +#define TRANSACTION(tag, value, name, ...) \ + case tag: \ return f.template operator()(); #include diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index 387aedecfc..5aa8b7216d 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -532,12 +532,28 @@ createPseudoAccount( [[nodiscard]] bool isPseudoAccount(std::shared_ptr sleAcct); +// Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if +// set +// Pseudo-account designator fields MUST be maintained by including the +// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to +// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated, +// since a non-active amendment will not set any field, by definition. +// Specific properties of a pseudo-account are NOT checked here, that's what +// InvariantCheck is for. +[[nodiscard]] std::vector const& +getPseudoAccountFields(); + [[nodiscard]] inline bool isPseudoAccount(ReadView const& view, AccountID accountId) { return isPseudoAccount(view.read(keylet::account(accountId))); } +[[nodiscard]] TER +canAddHolding(ReadView const& view, Asset const& asset); + +/// Any transactors that call addEmptyHolding() in doApply must call +/// canAddHolding() in preflight with the same View and Asset [[nodiscard]] TER addEmptyHolding( ApplyView& view, diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index d248d37e18..077b1a172a 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -629,8 +629,8 @@ xrpLiquid( std::uint32_t const ownerCount = confineOwnerCount( view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj); - // AMMs have no reserve requirement - auto const reserve = sle->isFieldPresent(sfAMMID) + // Pseudo-accounts have no reserve requirement + auto const reserve = isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount); @@ -1029,7 +1029,7 @@ adjustOwnerCount( AccountID const id = (*sle)[sfAccount]; std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j); view.adjustOwnerCountHook(id, current, adjusted); - sle->setFieldU32(sfOwnerCount, adjusted); + sle->at(sfOwnerCount) = adjusted; view.update(sle); } @@ -1069,15 +1069,47 @@ pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey) return beast::zero; } -// Note, the list of the pseudo-account designator fields below MUST be -// maintained but it does NOT need to be amendment-gated, since a -// non-active amendment will not set any field, by definition. Specific -// properties of a pseudo-account are NOT checked here, that's what +// Pseudo-account designator fields MUST be maintained by including the +// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to +// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated, +// since a non-active amendment will not set any field, by definition. +// Specific properties of a pseudo-account are NOT checked here, that's what // InvariantCheck is for. -static std::array const pseudoAccountOwnerFields = { - &sfAMMID, // - &sfVaultID, // -}; +[[nodiscard]] std::vector const& +getPseudoAccountFields() +{ + static std::vector const pseudoFields = []() { + auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT); + if (!ar) + LogicError( + "ripple::isPseudoAccount : unable to find account root ledger " + "format"); + auto const& soTemplate = ar->getSOTemplate(); + + std::vector pseudoFields; + for (auto const& field : soTemplate) + { + if (field.sField().shouldMeta(SField::sMD_PseudoAccount)) + pseudoFields.emplace_back(&field.sField()); + } + return pseudoFields; + }(); + return pseudoFields; +} + +[[nodiscard]] bool +isPseudoAccount(std::shared_ptr sleAcct) +{ + auto const& fields = getPseudoAccountFields(); + + // Intentionally use defensive coding here because it's cheap and makes the + // semantics of true return value clean. + return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT && + std::count_if( + fields.begin(), fields.end(), [&sleAcct](SField const* sf) -> bool { + return sleAcct->isFieldPresent(*sf); + }) > 0; +} Expected, TER> createPseudoAccount( @@ -1085,10 +1117,11 @@ createPseudoAccount( uint256 const& pseudoOwnerKey, SField const& ownerField) { + auto const& fields = getPseudoAccountFields(); XRPL_ASSERT( std::count_if( - pseudoAccountOwnerFields.begin(), - pseudoAccountOwnerFields.end(), + fields.begin(), + fields.end(), [&ownerField](SField const* sf) -> bool { return *sf == ownerField; }) == 1, @@ -1124,18 +1157,42 @@ createPseudoAccount( return account; } -[[nodiscard]] bool -isPseudoAccount(std::shared_ptr sleAcct) +[[nodiscard]] TER +canAddHolding(ReadView const& view, Issue const& issue) { - // Intentionally use defensive coding here because it's cheap and makes the - // semantics of true return value clean. - return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT && - std::count_if( - pseudoAccountOwnerFields.begin(), - pseudoAccountOwnerFields.end(), - [&sleAcct](SField const* sf) -> bool { - return sleAcct->isFieldPresent(*sf); - }) > 0; + if (issue.native()) + return tesSUCCESS; // No special checks for XRP + + auto const issuer = view.read(keylet::account(issue.getIssuer())); + if (!issuer) + return terNO_ACCOUNT; + else if (!issuer->isFlag(lsfDefaultRipple)) + return terNO_RIPPLE; + + return tesSUCCESS; +} + +[[nodiscard]] TER +canAddHolding(ReadView const& view, MPTIssue const& mptIssue) +{ + auto mptID = mptIssue.getMptID(); + auto issuance = view.read(keylet::mptIssuance(mptID)); + if (!issuance) + return tecOBJECT_NOT_FOUND; + if (!issuance->isFlag(lsfMPTCanTransfer)) + return tecNO_AUTH; + + return tesSUCCESS; +} + +[[nodiscard]] TER +canAddHolding(ReadView const& view, Asset const& asset) +{ + return std::visit( + [&](TIss const& issue) -> TER { + return canAddHolding(view, issue); + }, + asset.value()); } [[nodiscard]] TER