diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 4a92504060..6bd9be880f 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -349,7 +349,10 @@ loanbroker(uint256 const& vaultKey) } Keylet -loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept; +loan( + AccountID const& borrower, + uint256 loanBrokerID, + std::uint32_t seq) noexcept; inline Keylet loan(uint256 const& vaultKey) diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 9544fe37bb..93388b28c4 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include @@ -98,70 +100,73 @@ std::uint16_t constexpr maxTransferFee = 50000; * * Example: 50% is 0.50 * bipsPerUnity = 5,000 bps. */ -std::uint32_t constexpr bipsPerUnity = 100 * 100; -std::uint32_t constexpr tenthBipsPerUnity = bipsPerUnity * 10; +Bips32 constexpr bipsPerUnity(100 * 100); +TenthBips32 constexpr tenthBipsPerUnity(bipsPerUnity.value() * 10); -constexpr std::uint32_t +constexpr Bips32 percentageToBips(std::uint32_t percentage) { - return percentage * bipsPerUnity / 100; + return Bips32(percentage * bipsPerUnity.value() / 100); } -constexpr std::uint32_t +constexpr TenthBips32 percentageToTenthBips(std::uint32_t percentage) { - return percentage * tenthBipsPerUnity / 100; + return TenthBips32(percentage * tenthBipsPerUnity.value() / 100); } -template +template constexpr T -bipsOfValue(T value, std::uint32_t bips) +bipsOfValue(T value, Bips bips) { - return value * bips / bipsPerUnity; + return value * bips.value() / bipsPerUnity.value(); } -template +template constexpr T -tenthBipsOfValue(T value, std::uint32_t bips) +tenthBipsOfValue(T value, TenthBips bips) { - return value * bips / tenthBipsPerUnity; + return value * bips.value() / tenthBipsPerUnity.value(); } /** The maximum management fee rate allowed by a loan broker in 1/10 bips. Valid values are between 0 and 10% inclusive. */ -std::uint16_t constexpr maxManagementFeeRate = percentageToTenthBips(10); -static_assert(maxManagementFeeRate == 10'000); +TenthBips16 constexpr maxManagementFeeRate( + unsafe_cast(percentageToTenthBips(10).value())); +static_assert(maxManagementFeeRate == TenthBips16(std::uint16_t(10'000u))); /** The maximum coverage rate required of a loan broker in 1/10 bips. Valid values are between 0 and 100% inclusive. */ -std::uint32_t constexpr maxCoverRate = percentageToTenthBips(100); -static_assert(maxCoverRate == 100'000); +TenthBips32 constexpr maxCoverRate = percentageToTenthBips(100); +static_assert(maxCoverRate == TenthBips32(100'000u)); /** The maximum overpayment fee on a loan in 1/10 bips. * Valid values are between 0 and 100% inclusive. */ -std::uint32_t constexpr maxOverpaymentFee = percentageToTenthBips(100); +TenthBips32 constexpr maxOverpaymentFee = percentageToTenthBips(100); /** The maximum premium added to the interest rate for late payments on a loan * in 1/10 bips. * * Valid values are between 0 and 100% inclusive. */ -std::uint32_t constexpr maxLateInterestRate = percentageToTenthBips(100); +TenthBips32 constexpr maxLateInterestRate = percentageToTenthBips(100); -/** The maximum interest rate charged for repaying a loan early in 1/10 bips. +/** The maximum close interest rate charged for repaying a loan early in 1/10 + * bips. * * Valid values are between 0 and 100% inclusive. */ -std::uint32_t constexpr maxCloseInterestRate = percentageToTenthBips(100); +TenthBips32 constexpr maxCloseInterestRate = percentageToTenthBips(100); -/** The maximum interest rate charged on loan overpayments in 1/10 bips. +/** The maximum overpayment interest rate charged on loan overpayments in 1/10 + * bips. * * Valid values are between 0 and 100% inclusive. */ -std::uint32_t constexpr maxOverpaymentInterestRate = percentageToTenthBips(100); +TenthBips32 constexpr maxOverpaymentInterestRate = percentageToTenthBips(100); /** The maximum length of a URI inside an NFT */ std::size_t constexpr maxTokenURILength = 256; diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 611dd41be0..8199ca46c2 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -122,267 +123,287 @@ field_code(SerializedTypeID id, int index) return (safe_cast(id) << 16) | index; } -// constexpr + // constexpr inline int field_code(int id, int index) { return (id << 16) | index; } -/** Identifies fields. + /** Identifies fields. - Fields are necessary to tag data in signed transactions so that - the binary format of the transaction can be canonicalized. All - SFields are created at compile time. + Fields are necessary to tag data in signed transactions so that + the binary format of the transaction can be canonicalized. All + SFields are created at compile time. - Each SField, once constructed, lives until program termination, and there - is only one instance per fieldType/fieldValue pair which serves the entire - application. -*/ -class SField -{ -public: - enum { - sMD_Never = 0x00, - sMD_ChangeOrig = 0x01, // original value when it changes - sMD_ChangeNew = 0x02, // new value when it changes - 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, // value is treated as base 10, overriding default 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 + Each SField, once constructed, lives until program termination, and + there is only one instance per fieldType/fieldValue pair which serves the + entire application. + */ + class SField + { + public: + enum { + sMD_Never = 0x00, + sMD_ChangeOrig = 0x01, // original value when it changes + sMD_ChangeNew = 0x02, // new value when it changes + 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, // value is treated as base 10, overriding + // default 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 + }; + + enum class IsSigning : unsigned char { no, yes }; + static IsSigning const notSigning = IsSigning::no; + + int const fieldCode; // (type<<16)|index + SerializedTypeID const fieldType; // STI_* + int const fieldValue; // Code number for protocol + std::string const fieldName; + int const fieldMeta; + int const fieldNum; + IsSigning const signingField; + Json::StaticString const jsonName; + + SField(SField const&) = delete; + SField& + operator=(SField const&) = delete; + SField(SField&&) = delete; + SField& + operator=(SField&&) = delete; + + public: + struct private_access_tag_t; // public, but still an implementation + // detail + + // These constructors can only be called from SField.cpp + SField( + private_access_tag_t, + SerializedTypeID tid, + int fv, + const char* fn, + int meta = sMD_Default, + IsSigning signing = IsSigning::yes); + explicit SField(private_access_tag_t, int fc); + + static const SField& + getField(int fieldCode); + static const SField& + getField(std::string const& fieldName); + static const SField& + getField(int type, int value) + { + return getField(field_code(type, value)); + } + + static const SField& + getField(SerializedTypeID type, int value) + { + return getField(field_code(type, value)); + } + + std::string const& + getName() const + { + return fieldName; + } + + bool + hasName() const + { + return fieldCode > 0; + } + + Json::StaticString const& + getJsonName() const + { + return jsonName; + } + + operator Json::StaticString const&() const + { + return jsonName; + } + + bool + isInvalid() const + { + return fieldCode == -1; + } + + bool + isUseful() const + { + return fieldCode > 0; + } + + bool + isBinary() const + { + return fieldValue < 256; + } + + // A discardable field is one that cannot be serialized, and + // should be discarded during serialization,like 'hash'. + // You cannot serialize an object's hash inside that object, + // but you can have it in the JSON representation. + bool + isDiscardable() const + { + return fieldValue > 256; + } + + int + getCode() const + { + return fieldCode; + } + int + getNum() const + { + return fieldNum; + } + static int + getNumFields() + { + return num; + } + + bool + shouldMeta(int c) const + { + return (fieldMeta & c) != 0; + } + + bool + shouldInclude(bool withSigningField) const + { + return (fieldValue < 256) && + (withSigningField || (signingField == IsSigning::yes)); + } + + bool + operator==(const SField& f) const + { + return fieldCode == f.fieldCode; + } + + bool + operator!=(const SField& f) const + { + return fieldCode != f.fieldCode; + } + + static int + compare(const SField& f1, const SField& f2); + + static std::map const& + getKnownCodeToField() + { + return knownCodeToField; + } + + private: + static int num; + static std::map knownCodeToField; + static std::map knownNameToField; }; - enum class IsSigning : unsigned char { no, yes }; - static IsSigning const notSigning = IsSigning::no; - - int const fieldCode; // (type<<16)|index - SerializedTypeID const fieldType; // STI_* - int const fieldValue; // Code number for protocol - std::string const fieldName; - int const fieldMeta; - int const fieldNum; - IsSigning const signingField; - Json::StaticString const jsonName; - - SField(SField const&) = delete; - SField& - operator=(SField const&) = delete; - SField(SField&&) = delete; - SField& - operator=(SField&&) = delete; - -public: - struct private_access_tag_t; // public, but still an implementation detail - - // These constructors can only be called from SField.cpp - SField( - private_access_tag_t, - SerializedTypeID tid, - int fv, - const char* fn, - int meta = sMD_Default, - IsSigning signing = IsSigning::yes); - explicit SField(private_access_tag_t, int fc); - - static const SField& - getField(int fieldCode); - static const SField& - getField(std::string const& fieldName); - static const SField& - getField(int type, int value) + /** A field with a type known at compile time. */ + template + struct TypedField : SField { - return getField(field_code(type, value)); + using type = T; + + template + explicit TypedField(private_access_tag_t pat, Args&&... args); + }; + + /** Indicate std::optional field semantics. */ + template + struct OptionaledField + { + TypedField const* f; + + explicit OptionaledField(TypedField const& f_) : f(&f_) + { + } + }; + + template + inline OptionaledField + operator~(TypedField const& f) + { + return OptionaledField(f); } - static const SField& - getField(SerializedTypeID type, int value) - { - return getField(field_code(type, value)); - } + //------------------------------------------------------------------------------ - std::string const& - getName() const - { - return fieldName; - } + //------------------------------------------------------------------------------ - bool - hasName() const - { - return fieldCode > 0; - } + using SF_UINT8 = TypedField>; + using SF_UINT16 = TypedField>; + using SF_UINT32 = TypedField>; + using SF_UINT64 = TypedField>; + using SF_UINT96 = TypedField>; + using SF_UINT128 = TypedField>; + using SF_UINT160 = TypedField>; + using SF_UINT192 = TypedField>; + using SF_UINT256 = TypedField>; + using SF_UINT384 = TypedField>; + using SF_UINT512 = TypedField>; - Json::StaticString const& - getJsonName() const - { - return jsonName; - } + // These BIPS and TENTHBIPS values are serialized as the underlying type. + // The tag is only applied when deserialized. + // + // Basis points (bips) values: + using SF_BIPS16 = TypedField>; + using SF_BIPS32 = TypedField>; + // Tenth of a basis point values: + using SF_TENTHBIPS16 = TypedField>; + using SF_TENTHBIPS32 = TypedField>; - operator Json::StaticString const&() const - { - return jsonName; - } + using SF_ACCOUNT = TypedField; + using SF_AMOUNT = TypedField; + using SF_ISSUE = TypedField; + using SF_CURRENCY = TypedField; + using SF_NUMBER = TypedField; + using SF_VL = TypedField; + using SF_VECTOR256 = TypedField; + using SF_XCHAIN_BRIDGE = TypedField; - bool - isInvalid() const - { - return fieldCode == -1; - } + //------------------------------------------------------------------------------ - bool - isUseful() const - { - return fieldCode > 0; - } - - bool - isBinary() const - { - return fieldValue < 256; - } - - // A discardable field is one that cannot be serialized, and - // should be discarded during serialization,like 'hash'. - // You cannot serialize an object's hash inside that object, - // but you can have it in the JSON representation. - bool - isDiscardable() const - { - return fieldValue > 256; - } - - int - getCode() const - { - return fieldCode; - } - int - getNum() const - { - return fieldNum; - } - static int - getNumFields() - { - return num; - } - - bool - shouldMeta(int c) const - { - return (fieldMeta & c) != 0; - } - - bool - shouldInclude(bool withSigningField) const - { - return (fieldValue < 256) && - (withSigningField || (signingField == IsSigning::yes)); - } - - bool - operator==(const SField& f) const - { - return fieldCode == f.fieldCode; - } - - bool - operator!=(const SField& f) const - { - return fieldCode != f.fieldCode; - } - - static int - compare(const SField& f1, const SField& f2); - - static std::map const& - getKnownCodeToField() - { - return knownCodeToField; - } - -private: - static int num; - static std::map knownCodeToField; - static std::map knownNameToField; -}; - -/** A field with a type known at compile time. */ -template -struct TypedField : SField -{ - using type = T; - - template - explicit TypedField(private_access_tag_t pat, Args&&... args); -}; - -/** Indicate std::optional field semantics. */ -template -struct OptionaledField -{ - TypedField const* f; - - explicit OptionaledField(TypedField const& f_) : f(&f_) - { - } -}; - -template -inline OptionaledField -operator~(TypedField const& f) -{ - return OptionaledField(f); -} - -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ - -using SF_UINT8 = TypedField>; -using SF_UINT16 = TypedField>; -using SF_UINT32 = TypedField>; -using SF_UINT64 = TypedField>; -using SF_UINT96 = TypedField>; -using SF_UINT128 = TypedField>; -using SF_UINT160 = TypedField>; -using SF_UINT192 = TypedField>; -using SF_UINT256 = TypedField>; -using SF_UINT384 = TypedField>; -using SF_UINT512 = TypedField>; - -using SF_ACCOUNT = TypedField; -using SF_AMOUNT = TypedField; -using SF_ISSUE = TypedField; -using SF_CURRENCY = TypedField; -using SF_NUMBER = TypedField; -using SF_VL = TypedField; -using SF_VECTOR256 = TypedField; -using SF_XCHAIN_BRIDGE = TypedField; - -//------------------------------------------------------------------------------ - -// Use macros for most SField construction to enforce naming conventions. + // Use macros for most SField construction to enforce naming conventions. #pragma push_macro("UNTYPED_SFIELD") #undef UNTYPED_SFIELD #pragma push_macro("TYPED_SFIELD") #undef TYPED_SFIELD +#pragma push_macro("INTERPRETED_SFIELD") +#undef INTERPRETED_SFIELD #define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ - extern SField const sfName; + extern SField const sfName; #define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ - extern SF_##stiSuffix const sfName; + extern SF_##stiSuffix const sfName; +#define INTERPRETED_SFIELD( \ + sfName, stiSuffix, fieldValue, stiInterpreted, ...) \ + extern SF_##stiInterpreted const sfName; -extern SField const sfInvalid; -extern SField const sfGeneric; + extern SField const sfInvalid; + extern SField const sfGeneric; #include +#undef INTERPRETED_SFIELD +#pragma pop_macro("INTERPRETED_SFIELD") #undef TYPED_SFIELD #pragma pop_macro("TYPED_SFIELD") #undef UNTYPED_SFIELD diff --git a/include/xrpl/protocol/STInteger.h b/include/xrpl/protocol/STInteger.h index 68e25be1c9..f89f803533 100644 --- a/include/xrpl/protocol/STInteger.h +++ b/include/xrpl/protocol/STInteger.h @@ -21,6 +21,7 @@ #define RIPPLE_PROTOCOL_STINTEGER_H_INCLUDED #include +#include #include namespace ripple { @@ -81,6 +82,13 @@ using STUInt16 = STInteger; using STUInt32 = STInteger; using STUInt64 = STInteger; +// Basis points (bips) values: +using SFBips16 = STInteger; +using SFBips32 = STInteger; +// Tenth of a basis point values: +using SFTenthBips16 = STInteger; +using SFTenthBips32 = STInteger; + template inline STInteger::STInteger(Integer v) : value_(v) { @@ -122,7 +130,7 @@ template inline bool STInteger::isDefault() const { - return value_ == 0; + return value_ == Integer{}; } template diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index 9bc13bad8c..14f276ce90 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 @@ -518,7 +518,8 @@ protected: // Constraint += and -= ValueProxy operators // to value types that support arithmetic operations template -concept IsArithmetic = std::is_arithmetic_v || std::is_same_v; +concept IsArithmetic = std::is_arithmetic_v || std::is_same_v || + std::is_same_v || std::is_same_v; template class STObject::ValueProxy : public Proxy 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/FeeUnits.h b/include/xrpl/protocol/Units.h similarity index 66% rename from include/xrpl/protocol/FeeUnits.h rename to include/xrpl/protocol/Units.h index 0cbf1b608a..0e2aee96f3 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,180 @@ 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 */ 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 +331,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 +350,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, const TaggedFee& q) +operator<<(std::basic_ostream& os, const ValueUnit& 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 +436,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 +450,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 +493,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 +554,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 +571,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) { @@ -567,4 +605,4 @@ unsafe_cast(Src s) noexcept } // 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 1d6cae9ecf..df6624c140 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 2c089a81de..3535630374 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -61,7 +61,7 @@ TYPED_SFIELD(sfHookEmitCount, UINT16, 18) TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19) TYPED_SFIELD(sfHookApiVersion, UINT16, 20) TYPED_SFIELD(sfLedgerFixType, UINT16, 21) -TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) +INTERPRETED_SFIELD(sfManagementFeeRate, UINT16, 22, TENTHBIPS16) // 32-bit integers (common) TYPED_SFIELD(sfNetworkID, UINT32, 1) @@ -123,12 +123,12 @@ TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 55) TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 56) TYPED_SFIELD(sfPaymentRemaining, UINT32, 57) TYPED_SFIELD(sfPaymentTotal, UINT32, 58) -TYPED_SFIELD(sfCoverRateMinimum, UINT32, 59) -TYPED_SFIELD(sfCoverRateLiquidation, UINT32, 60) -TYPED_SFIELD(sfInterestRate, UINT32, 61) -TYPED_SFIELD(sfLateInterestRate, UINT32, 62) -TYPED_SFIELD(sfCloseInterestRate, UINT32, 63) -TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 64) +INTERPRETED_SFIELD(sfCoverRateMinimum, UINT32, 59, TENTHBIPS32) +INTERPRETED_SFIELD(sfCoverRateLiquidation, UINT32, 60, TENTHBIPS32) +INTERPRETED_SFIELD(sfInterestRate, UINT32, 61, TENTHBIPS32) +INTERPRETED_SFIELD(sfLateInterestRate, UINT32, 62, TENTHBIPS32) +INTERPRETED_SFIELD(sfCloseInterestRate, UINT32, 63, TENTHBIPS32) +INTERPRETED_SFIELD(sfOverpaymentInterestRate, UINT32, 64, TENTHBIPS32) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 2c16657a87..483d1fc03a 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -739,9 +739,11 @@ TRANSACTION(ttLOAN_SET, 78, LoanSet, noPriv, ({ {sfLoanServiceFee, soeOPTIONAL}, {sfLatePaymentFee, soeOPTIONAL}, {sfClosePaymentFee, soeOPTIONAL}, + {sfOverpaymentFee, soeOPTIONAL}, {sfInterestRate, soeOPTIONAL}, {sfLateInterestRate, soeOPTIONAL}, {sfCloseInterestRate, soeOPTIONAL}, + {sfOverpaymentInterestRate, soeOPTIONAL}, {sfPrincipalRequested, soeREQUIRED}, {sfStartDate, soeREQUIRED}, {sfPaymentTotal, soeOPTIONAL}, diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index 14c24f0c69..80f26d8e9d 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -559,9 +559,12 @@ loanbroker(AccountID const& owner, std::uint32_t seq) noexcept } Keylet -loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept +loan( + AccountID const& borrower, + uint256 loanBrokerID, + std::uint32_t seq) noexcept { - return loan(indexHash(LedgerNameSpace::LOAN, loanBrokerID, seq)); + return loan(indexHash(LedgerNameSpace::LOAN, borrower, loanBrokerID, seq)); } Keylet diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 8846682ba2..a43ff8bdba 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -54,6 +54,8 @@ TypedField::TypedField(private_access_tag_t pat, Args&&... args) #undef UNTYPED_SFIELD #pragma push_macro("TYPED_SFIELD") #undef TYPED_SFIELD +#pragma push_macro("INTERPRETED_SFIELD") +#undef INTERPRETED_SFIELD #define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ SField const sfName( \ @@ -69,6 +71,13 @@ TypedField::TypedField(private_access_tag_t pat, Args&&... args) fieldValue, \ std::string_view(#sfName).substr(2).data(), \ ##__VA_ARGS__); +#define INTERPRETED_SFIELD(sfName, stiSuffix, fieldValue, stiInterpreted, ...) \ + SF_##stiInterpreted const sfName( \ + access, \ + STI_##stiSuffix, \ + fieldValue, \ + std::string_view(#sfName).substr(2).data(), \ + ##__VA_ARGS__); // SFields which, for historical reasons, do not follow naming conventions. SField const sfInvalid(access, -1); @@ -80,6 +89,8 @@ SField const sfIndex(access, STI_UINT256, 258, "index"); #include +#undef INTERPRETED_SFIELD +#pragma pop_macro("INTERPRETED_SFIELD") #undef TYPED_SFIELD #pragma pop_macro("TYPED_SFIELD") #undef UNTYPED_SFIELD diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 7250a158e2..01eeb1c927 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -686,7 +686,10 @@ STObject STObject::getFieldObject(SField const& field) const { STObject const empty{field}; - return getFieldByConstRef(field, empty); + auto ret = getFieldByConstRef(field, empty); + if (ret != empty) + ret.applyTemplateFromSField(field); + return ret; } const STArray& diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index a680037880..3b7e4f3a7d 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -75,7 +75,14 @@ class LoanBroker_test : public beast::unit_test::suite env(set(alice, keylet.key), fee(increment), ter(temDISABLED)); auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); - // LoanBrokerDelete is disabled, too. + // Other LoanBroker transactions are disabled, too. + // 1. LoanBrokerCoverDeposit + env(coverDeposit(alice, brokerKeylet.key, asset(1000)), + ter(temDISABLED)); + // 2. LoanBrokerCoverWithdraw + env(coverWithdraw(alice, brokerKeylet.key, asset(1000)), + ter(temDISABLED)); + // 3. LoanBrokerDelete env(del(alice, brokerKeylet.key), ter(temDISABLED)); }; failAll(all - featureMPTokensV1); @@ -421,7 +428,7 @@ class LoanBroker_test : public beast::unit_test::suite ter(tecNO_PERMISSION)); // sfManagementFeeRate: too big env(set(evan, vault.vaultID), - managementFeeRate(maxManagementFeeRate + 1), + managementFeeRate(maxManagementFeeRate + TenthBips16(10)), fee(increment), ter(temINVALID)); // sfCoverRateMinimum: good value, bad account @@ -555,10 +562,10 @@ class LoanBroker_test : public beast::unit_test::suite return env.jt( jv, data(testData), - managementFeeRate(123), + managementFeeRate(TenthBips16(123)), debtMaximum(Number(9)), - coverRateMinimum(100), - coverRateLiquidation(200)); + coverRateMinimum(TenthBips32(100)), + coverRateLiquidation(TenthBips32(200))); }, [&](SLE::const_ref broker) { // Extra checks 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/jtx/JTx.h b/src/test/jtx/JTx.h index 198839dd28..1c3ea0a47b 100644 --- a/src/test/jtx/JTx.h +++ b/src/test/jtx/JTx.h @@ -54,7 +54,9 @@ struct JTx bool fill_sig = true; bool fill_netid = true; std::shared_ptr stx; + // TODO: Remove std::function signer; + std::vector> signers; JTx() = default; JTx(JTx const&) = default; diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 17a7b12c38..5b3d2fb593 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -164,6 +165,37 @@ struct blobField : public JTxField } }; +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); + static_assert(std::is_same_v< + OV, + typename SField::type::value_type::value_type::value_type>); + +protected: + using base::value_; + +public: + explicit valueUnitField(SF const& sfield, SV const& value) + : JTxField(sfield, value) + { + } + + OV + value() const override + { + return value_.value(); + } +}; + template struct JTxFieldWrapper { @@ -221,6 +253,13 @@ public: } }; +template < + class SField, + class UnitTag = typename SField::type::value_type::unit_type, + class ValueType = typename SField::type::value_type::value_type> +using valueUnitWrapper = + JTxFieldWrapper>; + template using simpleField = JTxFieldWrapper>; @@ -627,17 +666,32 @@ auto const loanBrokerID = JTxFieldWrapper(sfLoanBrokerID); auto const data = JTxFieldWrapper(sfData); -auto const managementFeeRate = simpleField(sfManagementFeeRate); +auto const managementFeeRate = + valueUnitWrapper(sfManagementFeeRate); auto const debtMaximum = simpleField(sfDebtMaximum); -auto const coverRateMinimum = simpleField(sfCoverRateMinimum); +auto const coverRateMinimum = + valueUnitWrapper(sfCoverRateMinimum); auto const coverRateLiquidation = - simpleField(sfCoverRateLiquidation); + simpleField(sfCoverRateLiquidation); } // namespace loanBroker +/* Loan */ +/******************************************************************************/ +namespace loan { + +Json::Value +set(AccountID const& account, + uint256 loanBrokerID, + Number principalRequested, + NetClock::time_point const& startDate, + uint32_t flags = 0); + +} + } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index c7510ea9e0..bc25103092 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 diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 257b0c606b..5c3bc21fad 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -470,6 +470,28 @@ coverWithdraw( } // namespace loanBroker +/* Loan */ +/******************************************************************************/ +namespace loan { + +Json::Value +set(AccountID const& account, + uint256 loanBrokerID, + Number principalRequested, + NetClock::time_point const& startDate, + uint32_t flags) +{ + Json::Value jv; + jv[sfTransactionType] = jss::LoanSet; + jv[sfAccount] = to_string(account); + jv[sfLoanBrokerID] = to_string(loanBrokerID); + jv[sfPrincipalRequested] = to_string(principalRequested); + jv[sfFlags] = flags; + jv[sfStartDate] = startDate.time_since_epoch().count(); + return jv; +} + +} // namespace loan } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index b515d37bed..ac8adf5a77 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -1382,7 +1382,11 @@ class Invariants_test : public beast::unit_test::suite sle->at(sfOwner) = sle->at(sfAccount); }, [](SLE::pointer& sle) { - sle->at(sfManagementFeeRate) += 1; + // The operator overloads aren't playing nice with the + // custom class, so just do it the hard way for now. + auto value = sle->at(sfManagementFeeRate).value(); + ++value; + sle->at(sfManagementFeeRate) = value; }, [](SLE::pointer& sle) { sle->at(sfCoverRateMinimum) += 1; }, [](SLE::pointer& sle) { diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index f27f0c2915..943065ac41 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -805,7 +805,7 @@ class Simulate_test : public beast::unit_test::suite testTx(env, tx, testSimulation); tx[sfSigningPubKey] = ""; - tx[sfTxnSignature] = ""; + tx[sfTxnSignature] = "x"; tx[sfSequence] = env.seq(env.master); tx[sfFee] = env.current()->fees().base.jsonClipped().asString(); 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 b2150a6d55..b21031c57e 100644 --- a/src/xrpld/app/tx/detail/DeleteAccount.cpp +++ b/src/xrpld/app/tx/detail/DeleteAccount.cpp @@ -30,10 +30,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 7e6925745a..a31f58c468 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -26,11 +26,11 @@ #include #include -#include #include #include #include #include +#include #include namespace ripple { diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp index 8f639eb529..c051ff42fd 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp @@ -125,6 +125,8 @@ LoanBrokerCoverDeposit::doApply() auto const amount = tx[sfAmount]; auto broker = view().peek(keylet::loanbroker(brokerID)); + if (!broker) + return tecINTERNAL; // LCOV_EXCL_LINE auto const brokerPseudoID = broker->at(sfAccount); diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp index 4b3102eef0..f47b607972 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp @@ -138,6 +138,8 @@ LoanBrokerCoverWithdraw::doApply() auto const amount = tx[sfAmount]; auto broker = view().peek(keylet::loanbroker(brokerID)); + if (!broker) + return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const brokerPseudoID = broker->at(sfAccount); diff --git a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp index e1dcebf3e9..d7b0293b60 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp @@ -96,8 +96,12 @@ LoanBrokerDelete::doApply() // Delete the loan broker auto broker = view().peek(keylet::loanbroker(brokerID)); + if (!broker) + return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const vaultID = broker->at(sfVaultID); auto const sleVault = view().read(keylet::vault(vaultID)); + if (!sleVault) + return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const vaultPseudoID = sleVault->at(sfAccount); auto const vaultAsset = sleVault->at(sfAsset); diff --git a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp index 857abc601b..49dd923672 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp @@ -155,6 +155,8 @@ LoanBrokerSet::doApply() { // Modify an existing LoanBroker auto broker = view.peek(keylet::loanbroker(*brokerID)); + if (!broker) + return tefBAD_LEDGER; // LCOV_EXCL_LINE if (auto const data = tx[~sfData]) broker->at(sfData) = *data; @@ -172,6 +174,8 @@ LoanBrokerSet::doApply() auto const sequence = tx.getSeqValue(); auto owner = view.peek(keylet::account(account_)); + if (!owner) + return tefBAD_LEDGER; // LCOV_EXCL_LINE auto broker = std::make_shared(keylet::loanbroker(account_, sequence)); @@ -180,10 +184,14 @@ LoanBrokerSet::doApply() if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode)) return ter; + /* We're already charging a higher fee, so we probably don't want to + also charge a reserve. + * adjustOwnerCount(view, owner, 1, j_); auto ownerCount = owner->at(sfOwnerCount); if (mPriorBalance < view.fees().accountReserve(ownerCount)) return tecINSUFFICIENT_RESERVE; + */ auto maybePseudo = createPseudoAccount( view, broker->key(), PseudoAccountOwnerType::LoanBroker); diff --git a/src/xrpld/app/tx/detail/LoanSet.cpp b/src/xrpld/app/tx/detail/LoanSet.cpp index 3f26671c41..34285b966c 100644 --- a/src/xrpld/app/tx/detail/LoanSet.cpp +++ b/src/xrpld/app/tx/detail/LoanSet.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -63,14 +64,9 @@ LoanSet::doPreflight(PreflightContext const& ctx) auto const& tx = ctx.tx; auto const counterPartySig = ctx.tx.getFieldObject(sfCounterpartySignature); - // Copied from preflight1 - // TODO: Refactor into a helper function? - if (auto const spk = counterPartySig.getFieldVL(sfSigningPubKey); - !spk.empty() && !publicKeyType(makeSlice(spk))) - { - JLOG(ctx.j.debug()) << "preflight1: invalid signing key"; - return temBAD_SIGNATURE; - } + if (auto const ret = + ripple::detail::preflightCheckSigningKey(counterPartySig, ctx.j)) + return ret; if (auto const data = tx[~sfData]; data && !data->empty() && !validDataLength(tx[~sfData], maxDataPayloadLength)) @@ -96,34 +92,9 @@ LoanSet::doPreflight(PreflightContext const& ctx) return temINVALID; // Copied from preflight2 - // TODO: Refactor into a helper function? - if (ctx.flags & tapDRY_RUN) // simulation - { - if (!ctx.tx.getSignature(counterPartySig).empty()) - { - // NOTE: This code should never be hit because it's checked in the - // `simulate` RPC - return temINVALID; // LCOV_EXCL_LINE - } - - if (!counterPartySig.isFieldPresent(sfSigners)) - { - // no signers, no signature - a valid simulation - return tesSUCCESS; - } - - for (auto const& signer : counterPartySig.getFieldArray(sfSigners)) - { - if (signer.isFieldPresent(sfTxnSignature) && - !signer[sfTxnSignature].empty()) - { - // NOTE: This code should never be hit because it's - // checked in the `simulate` RPC - return temINVALID; // LCOV_EXCL_LINE - } - } - return tesSUCCESS; - } + if (auto const ret = ripple::detail::preflightCheckSimulateKeys( + ctx.flags, counterPartySig, ctx.j)) + return *ret; return tesSUCCESS; } @@ -152,7 +123,6 @@ LoanSet::checkSign(PreclaimContext const& ctx) auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature); return Transactor::checkSign(ctx, *counterSigner, counterSig); } -} XRPAmount LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx) @@ -176,120 +146,247 @@ LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx) TER LoanSet::preclaim(PreclaimContext const& ctx) { - return temDISABLED; - auto const& tx = ctx.tx; + if (auto const startDate(tx[sfStartDate]); hasExpired(ctx.view, startDate)) + { + JLOG(ctx.j.warn()) << "Start date is in the past."; + return tecEXPIRED; + } + auto const account = tx[sfAccount]; - if (auto const ID = tx[~sfLoanID]) + auto const brokerID = tx[sfLoanBrokerID]; + + auto const brokerSle = ctx.view.read(keylet::loanbroker(brokerID)); + if (!brokerSle) { - auto const sle = ctx.view.read(keylet::loan(*ID)); - if (!sle) - { - JLOG(ctx.j.warn()) << "Loan does not exist."; - return tecNO_ENTRY; - } - if (tx[sfVaultID] != sle->at(sfVaultID)) - { - JLOG(ctx.j.warn()) << "Can not change VaultID on an existing Loan."; - return tecNO_PERMISSION; - } - if (account != sle->at(sfOwner)) - { - JLOG(ctx.j.warn()) << "Account is not the owner of the Loan."; - return tecNO_PERMISSION; - } + JLOG(ctx.j.warn()) << "LoanBroker does not exist."; + return tecNO_ENTRY; } - else + auto const brokerOwner = brokerSle->at(sfOwner); + if (!tx.isFieldPresent(sfCounterparty) && account != brokerOwner) { - auto const vaultID = tx[sfVaultID]; - auto const sleVault = ctx.view.read(keylet::vault(vaultID)); - if (!sleVault) - { - JLOG(ctx.j.warn()) << "Vault does not exist."; - return tecNO_ENTRY; - } - if (account != sleVault->at(sfOwner)) - { - JLOG(ctx.j.warn()) << "Account is not the owner of the Vault."; - return tecNO_PERMISSION; - } + JLOG(ctx.j.warn()) + << "Counterparty is not the owner of the LoanBroker."; + return tecNO_PERMISSION; } + auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner); + auto const borrower = counterparty == brokerOwner ? account : counterparty; + if (account != brokerOwner && counterparty != brokerOwner) + { + JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner " + "of the LoanBroker."; + return tecNO_PERMISSION; + } + + if (auto const borrowerSle = ctx.view.read(keylet::account(borrower)); + !borrowerSle) + { + JLOG(ctx.j.warn()) << "Borrower does not exist."; + return tecNO_ENTRY; + } + + auto const brokerPseudo = brokerSle->at(sfAccount); + auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID))); + if (!vault) + // Should be impossible + return tefBAD_LEDGER; // LCOV_EXCL_LINE + auto const asset = vault->at(sfAsset); + if (isFrozen(ctx.view, brokerOwner, asset) || + isFrozen(ctx.view, brokerPseudo, asset)) + { + JLOG(ctx.j.warn()) << "One of the affected accounts is frozen."; + return asset.holds() ? tecFROZEN : tecLOCKED; + } + if (asset.holds()) + { + auto const issue = asset.get(); + if (isDeepFrozen(ctx.view, borrower, issue.currency, issue.account)) + return tecFROZEN; + } + auto const principalRequested = tx[sfPrincipalRequested]; + if (auto const assetsAvailable = vault->at(sfAssetsAvailable); + assetsAvailable < principalRequested) + { + JLOG(ctx.j.warn()) + << "Insufficient assets available in the Vault to fund the loan."; + return tecINSUFFICIENT_FUNDS; + } + auto const debtTotal = brokerSle->at(sfDebtTotal); + if (brokerSle->at(sfDebtMaximum) < debtTotal + principalRequested) + { + JLOG(ctx.j.warn()) + << "Loan would exceed the maximum debt limit of the LoanBroker."; + return tecLIMIT_EXCEEDED; + } + if (brokerSle->at(sfCoverAvailable) < + tenthBipsOfValue( + debtTotal + principalRequested, brokerSle->at(sfCoverRateMinimum))) + { + JLOG(ctx.j.warn()) + << "Insufficient first-loss capital to cover the loan."; + return tecINSUFFICIENT_FUNDS; + } + return tesSUCCESS; } TER LoanSet::doApply() { - return temDISABLED; - auto const& tx = ctx_.tx; auto& view = ctx_.view(); -#if 0 - if (auto const ID = tx[~sfLoanID]) + auto const brokerID = tx[sfLoanBrokerID]; + + auto const brokerSle = view.peek(keylet::loanbroker(brokerID)); + if (!brokerSle) + tefBAD_LEDGER; // LCOV_EXCL_LINE + auto const brokerOwner = brokerSle->at(sfOwner); + auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner)); + + auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID))); + if (!vaultSle) + return tefBAD_LEDGER; // LCOV_EXCL_LINE + auto const vaultPseudo = vaultSle->at(sfAccount); + auto const vaultAsset = vaultSle->at(sfAsset); + + auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner); + auto const borrower = counterparty == brokerOwner ? account_ : counterparty; + auto const borrowerSle = view.peek(keylet::account(borrower)); + if (!borrowerSle) { - // Modify an existing Loan - auto loan = view.peek(keylet::loan(*ID)); - - if (auto const data = tx[~sfData]) - loan->at(sfData) = *data; - if (auto const debtMax = tx[~sfDebtMaximum]) - loan->at(sfDebtMaximum) = *debtMax; - - view.update(); + return tefBAD_LEDGER; // LCOV_EXCL_LINE } - else + + auto const brokerPseudo = brokerSle->at(sfAccount); + auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo)); + auto const principalRequested = tx[sfPrincipalRequested]; + auto const interestRate = tx[~sfInterestRate].value_or(TenthBips32(0)); + auto const originationFee = tx[~sfLoanOriginationFee]; + auto const loanAssetsAvailable = + principalRequested - originationFee.value_or(Number{}); + + adjustOwnerCount(view, borrowerSle, 1, j_); + auto ownerCount = borrowerSle->at(sfOwnerCount); + if (mPriorBalance < view.fees().accountReserve(ownerCount)) + return tecINSUFFICIENT_RESERVE; + + // Create a holding for the borrower if one does not already exist. + + // Account for the origination fee using two payments + // + // 1. Transfer (principalRequested - originationFee) from vault + // pseudo-account to LoanBroker pseudo-account. + // + // Create the holding if it doesn't already exist + if (auto const ter = addEmptyHolding( + view, + brokerPseudo, + brokerPseudoSle->at(sfBalance).value().xrp(), + vaultAsset, + j_); + ter != tesSUCCESS && ter != tecDUPLICATE) + // ignore tecDUPLICATE. That means the holding already exists, and is + // fine here + return ter; + if (auto const ter = accountSend( + view, + vaultPseudo, + brokerPseudo, + STAmount{vaultAsset, loanAssetsAvailable}, + j_, + WaiveTransferFee::Yes)) + return ter; + // 2. Transfer originationFee, if any, from vault pseudo-account to + // LoanBroker owner. + if (originationFee) { - // Create a new Loan pointing back to the given Vault - auto const vaultID = tx[sfVaultID]; - auto const sleVault = view.read(keylet::vault(vaultID)); - auto const vaultPseudoID = sleVault->at(sfAccount); - auto const sequence = tx.getSeqValue(); - - auto owner = view.peek(keylet::account(account_)); - auto loan = std::make_shared(keylet::loan(account_, sequence)); - - if (auto const ter = dirLink(view, account_, )) + // Create the holding if it doesn't already exist + if (auto const ter = addEmptyHolding( + view, + brokerOwner, + brokerOwnerSle->at(sfBalance).value().xrp(), + vaultAsset, + j_); + ter != tesSUCCESS && ter != tecDUPLICATE) + // ignore tecDUPLICATE. That means the holding already exists, and + // is fine here return ter; - if (auto const ter = dirLink(view, vaultPseudoID, , sfVaultNode)) + if (auto const ter = accountSend( + view, + vaultPseudo, + brokerOwner, + STAmount{vaultAsset, *originationFee}, + j_, + WaiveTransferFee::Yes)) return ter; - - adjustOwnerCount(view, owner, 1, j_); - auto ownerCount = owner->at(sfOwnerCount); - if (mPriorBalance < view.fees().accountReserve(ownerCount)) - return tecINSUFFICIENT_RESERVE; - - auto maybePseudo = - createPseudoAccount(view, loan->key(), PseudoAccountOwnerType::Loan); - if (!maybePseudo) - return maybePseudo.error(); - auto& pseudo = *maybePseudo; - auto pseudoId = pseudo->at(sfAccount); - - if (auto ter = addEmptyHolding( - view, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_)) - return ter; - - // Initialize data fields: - loan->at(sfSequence) = sequence; - loan->at(sfVaultID) = vaultID; - loan->at(sfOwner) = account_; - loan->at(sfAccount) = pseudoId; - if (auto const data = tx[~sfData]) - loan->at(sfData) = *data; - if (auto const rate = tx[~sfManagementFeeRate]) - loan->at(sfManagementFeeRate) = *rate; - if (auto const debtMax = tx[~sfDebtMaximum]) - loan->at(sfDebtMaximum) = *debtMax; - if (auto const coverMin = tx[~sfCoverRateMinimum]) - loan->at(sfCoverRateMinimum) = *coverMin; - if (auto const coverLiq = tx[~sfCoverRateLiquidation]) - loan->at(sfCoverRateLiquidation) = *coverLiq; - - view.insert(loan); } -#endif + + auto const managementFeeRate = brokerSle->at(sfManagementFeeRate); + auto const loanInterest = + tenthBipsOfValue(principalRequested, interestRate); + auto const loanInterestToVault = loanInterest - + tenthBipsOfValue(loanInterest, managementFeeRate.value()); + auto const loanSequence = tx.getSeqValue(); + auto const startDate = tx[sfStartDate]; + auto const paymentInterval = + tx[~sfPaymentInterval].value_or(defaultPaymentInterval); + + // Create the loan + auto loan = + std::make_shared(keylet::loan(borrower, brokerID, loanSequence)); + + // Prevent copy/paste errors + auto setLoanField = [&loan, &tx](auto const& field) { + loan->at(field) = tx[field]; + }; + + // Set required tx fields and pre-computed fields + loan->at(sfPrincipalRequested) = principalRequested; + loan->at(sfStartDate) = startDate; + loan->at(sfSequence) = loanSequence; + loan->at(sfLoanBrokerID) = brokerID; + loan->at(sfBorrower) = borrower; + loan->at(~sfLoanOriginationFee) = originationFee; + loan->at(sfPaymentInterval) = paymentInterval; + // Set all other transaction fields directly from the transaction + if (tx.isFlag(tfLoanOverpayment)) + loan->setFlag(lsfLoanOverpayment); + setLoanField(~sfData); + setLoanField(~sfLoanServiceFee); + setLoanField(~sfLatePaymentFee); + setLoanField(~sfClosePaymentFee); + setLoanField(~sfOverpaymentFee); + setLoanField(~sfInterestRate); + setLoanField(~sfLateInterestRate); + setLoanField(~sfCloseInterestRate); + setLoanField(~sfOverpaymentInterestRate); + loan->at(sfGracePeriod) = tx[~sfGracePeriod].value_or(defaultGracePeriod); + // Set dynamic fields to their initial values + loan->at(sfPreviousPaymentDate) = 0; + loan->at(sfNextPaymentDueDate) = startDate + paymentInterval; + loan->at(sfPaymentRemaining) = + tx[~sfPaymentTotal].value_or(defaultPaymentTotal); + loan->at(sfAssetsAvailable) = loanAssetsAvailable; + loan->at(sfPrincipalOutstanding) = principalRequested; + + // Update the balances in the vault + vaultSle->at(sfAssetsAvailable) -= principalRequested; + vaultSle->at(sfAssetsTotal) += loanInterestToVault; + view.update(vaultSle); + + // Update the balances in the loan broker + brokerSle->at(sfDebtTotal) += principalRequested + loanInterestToVault; + adjustOwnerCount(view, brokerSle, 1, j_); + + if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode)) + return ter; + // Borrower is effectively the owner of the loan + if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode)) + return ter; + + view.update(brokerSle); return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 48f7434ff5..c08022dbea 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -87,6 +87,22 @@ preflight0(PreflightContext const& ctx, std::uint32_t flagMask) namespace detail { +/** Checks the validity of the transactor signing key. + * + * Normally called from preflight1. + */ +NotTEC +preflightCheckSigningKey(STObject const& sigObject, beast::Journal j) +{ + if (auto const spk = sigObject.getFieldVL(sfSigningPubKey); + !spk.empty() && !publicKeyType(makeSlice(spk))) + { + JLOG(j.debug()) << "preflightCheckSigningKey: invalid signing key"; + return temBAD_SIGNATURE; + } + return tesSUCCESS; +} + /** Performs early sanity checks on the account and fee fields */ NotTEC preflight1(PreflightContext const& ctx, std::uint32_t flagMask) @@ -118,13 +134,8 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask) return temBAD_FEE; } - auto const spk = ctx.tx.getSigningPubKey(); - - if (!spk.empty() && !publicKeyType(makeSlice(spk))) - { - JLOG(ctx.j.debug()) << "preflight1: invalid signing key"; - return temBAD_SIGNATURE; - } + if (auto const ret = preflightCheckSigningKey(ctx.tx, ctx.j)) + return ret; // An AccountTxnID field constrains transaction ordering more than the // Sequence field. Tickets, on the other hand, reduce ordering @@ -139,26 +150,28 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask) return tesSUCCESS; } -/** Checks whether the signature appears valid */ -NotTEC -preflight2(PreflightContext const& ctx) +std::optional +preflightCheckSimulateKeys( + ApplyFlags flags, + STObject const& sigObject, + beast::Journal j) { - if (ctx.flags & tapDRY_RUN) // simulation + if (flags & tapDRY_RUN) // simulation { - if (!ctx.tx.getSignature().empty()) + if (!sigObject.getFieldVL(sfTxnSignature).empty()) { // NOTE: This code should never be hit because it's checked in the // `simulate` RPC return temINVALID; // LCOV_EXCL_LINE } - if (!ctx.tx.isFieldPresent(sfSigners)) + if (!sigObject.isFieldPresent(sfSigners)) { // no signers, no signature - a valid simulation return tesSUCCESS; } - for (auto const& signer : ctx.tx.getFieldArray(sfSigners)) + for (auto const& signer : sigObject.getFieldArray(sfSigners)) { if (signer.isFieldPresent(sfTxnSignature) && !signer[sfTxnSignature].empty()) @@ -170,6 +183,17 @@ preflight2(PreflightContext const& ctx) } return tesSUCCESS; } + return {}; +} + +/** Checks whether the signature appears valid */ +NotTEC +preflight2(PreflightContext const& ctx) +{ + if (auto const ret = preflightCheckSimulateKeys(ctx.flags, ctx.tx, ctx.j)) + // Skips following checks if the transaction is being simulated, + // regardless of success or failure + return *ret; auto const sigValid = checkValidity( ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config()); diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index 8e7f4a1a92..1e57bd02ba 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -262,6 +262,14 @@ NotTEC preflight0(PreflightContext const& ctx, std::uint32_t flagMask); namespace detail { + +/** Checks the validity of the transactor signing key. + * + * Normally called from preflight1 with ctx.tx. + */ +NotTEC +preflightCheckSigningKey(STObject const& sigObject, beast::Journal j); + /** Performs early sanity checks on the account and fee fields. (And passes flagMask to preflight0) @@ -269,6 +277,16 @@ namespace detail { NotTEC preflight1(PreflightContext const& ctx, std::uint32_t flagMask); +/** Checks the special signing key state needed for simulation + * + * Normally called from preflight2 with ctx.tx. + */ +std::optional +preflightCheckSimulateKeys( + ApplyFlags flags, + STObject const& sigObject, + beast::Journal j); + /** Checks whether the signature appears valid */ NotTEC preflight2(PreflightContext const& ctx);