[WIP] Continue implementing LoanSet Transactor

- Needs tests
- Started trying to add strong typing to the fields used as bips or 1/10
  bips.
This commit is contained in:
Ed Hennis
2025-04-21 17:39:48 -04:00
parent cbb14627ea
commit 89f0b3b9e4
31 changed files with 895 additions and 556 deletions

View File

@@ -349,7 +349,10 @@ loanbroker(uint256 const& vaultKey)
} }
Keylet 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 inline Keylet
loan(uint256 const& vaultKey) loan(uint256 const& vaultKey)

View File

@@ -23,6 +23,8 @@
#include <xrpl/basics/ByteUtilities.h> #include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/partitioned_unordered_map.h> #include <xrpl/basics/partitioned_unordered_map.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/protocol/Units.h>
#include <cstdint> #include <cstdint>
@@ -98,70 +100,73 @@ std::uint16_t constexpr maxTransferFee = 50000;
* *
* Example: 50% is 0.50 * bipsPerUnity = 5,000 bps. * Example: 50% is 0.50 * bipsPerUnity = 5,000 bps.
*/ */
std::uint32_t constexpr bipsPerUnity = 100 * 100; Bips32 constexpr bipsPerUnity(100 * 100);
std::uint32_t constexpr tenthBipsPerUnity = bipsPerUnity * 10; TenthBips32 constexpr tenthBipsPerUnity(bipsPerUnity.value() * 10);
constexpr std::uint32_t constexpr Bips32
percentageToBips(std::uint32_t percentage) 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) percentageToTenthBips(std::uint32_t percentage)
{ {
return percentage * tenthBipsPerUnity / 100; return TenthBips32(percentage * tenthBipsPerUnity.value() / 100);
} }
template <typename T> template <typename T, class TBips>
constexpr T constexpr T
bipsOfValue(T value, std::uint32_t bips) bipsOfValue(T value, Bips<TBips> bips)
{ {
return value * bips / bipsPerUnity; return value * bips.value() / bipsPerUnity.value();
} }
template <typename T> template <typename T, class TBips>
constexpr T constexpr T
tenthBipsOfValue(T value, std::uint32_t bips) tenthBipsOfValue(T value, TenthBips<TBips> 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. /** The maximum management fee rate allowed by a loan broker in 1/10 bips.
Valid values are between 0 and 10% inclusive. Valid values are between 0 and 10% inclusive.
*/ */
std::uint16_t constexpr maxManagementFeeRate = percentageToTenthBips(10); TenthBips16 constexpr maxManagementFeeRate(
static_assert(maxManagementFeeRate == 10'000); unsafe_cast<std::uint16_t>(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. /** The maximum coverage rate required of a loan broker in 1/10 bips.
Valid values are between 0 and 100% inclusive. Valid values are between 0 and 100% inclusive.
*/ */
std::uint32_t constexpr maxCoverRate = percentageToTenthBips(100); TenthBips32 constexpr maxCoverRate = percentageToTenthBips(100);
static_assert(maxCoverRate == 100'000); static_assert(maxCoverRate == TenthBips32(100'000u));
/** The maximum overpayment fee on a loan in 1/10 bips. /** The maximum overpayment fee on a loan in 1/10 bips.
* *
Valid values are between 0 and 100% inclusive. 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 /** The maximum premium added to the interest rate for late payments on a loan
* in 1/10 bips. * in 1/10 bips.
* *
* Valid values are between 0 and 100% inclusive. * 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. * 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. * 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 */ /** The maximum length of a URI inside an NFT */
std::size_t constexpr maxTokenURILength = 256; std::size_t constexpr maxTokenURILength = 256;

View File

@@ -22,6 +22,7 @@
#include <xrpl/basics/safe_cast.h> #include <xrpl/basics/safe_cast.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
#include <xrpl/protocol/Units.h>
#include <cstdint> #include <cstdint>
#include <map> #include <map>
@@ -122,36 +123,38 @@ field_code(SerializedTypeID id, int index)
return (safe_cast<int>(id) << 16) | index; return (safe_cast<int>(id) << 16) | index;
} }
// constexpr // constexpr
inline int inline int
field_code(int id, int index) field_code(int id, int index)
{ {
return (id << 16) | index; return (id << 16) | index;
} }
/** Identifies fields. /** Identifies fields.
Fields are necessary to tag data in signed transactions so that Fields are necessary to tag data in signed transactions so that
the binary format of the transaction can be canonicalized. All the binary format of the transaction can be canonicalized. All
SFields are created at compile time. SFields are created at compile time.
Each SField, once constructed, lives until program termination, and there Each SField, once constructed, lives until program termination, and
is only one instance per fieldType/fieldValue pair which serves the entire there is only one instance per fieldType/fieldValue pair which serves the
application. entire application.
*/ */
class SField class SField
{ {
public: public:
enum { enum {
sMD_Never = 0x00, sMD_Never = 0x00,
sMD_ChangeOrig = 0x01, // original value when it changes sMD_ChangeOrig = 0x01, // original value when it changes
sMD_ChangeNew = 0x02, // new value when it changes sMD_ChangeNew = 0x02, // new value when it changes
sMD_DeleteFinal = 0x04, // final value when it is deleted sMD_DeleteFinal = 0x04, // final value when it is deleted
sMD_Create = 0x08, // value when it's created sMD_Create = 0x08, // value when it's created
sMD_Always = 0x10, // value when node containing it is affected at all sMD_Always =
sMD_BaseTen = 0x10, // value when node containing it is affected at all
0x20, // value is treated as base 10, overriding default behavior sMD_BaseTen = 0x20, // value is treated as base 10, overriding
sMD_PseudoAccount = 0x40, // if this field is set in an ACCOUNT_ROOT // default behavior
sMD_PseudoAccount =
0x40, // if this field is set in an ACCOUNT_ROOT
// _only_, then it is a pseudo-account // _only_, then it is a pseudo-account
sMD_Default = sMD_Default =
sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create
@@ -176,8 +179,9 @@ public:
SField& SField&
operator=(SField&&) = delete; operator=(SField&&) = delete;
public: public:
struct private_access_tag_t; // public, but still an implementation detail struct private_access_tag_t; // public, but still an implementation
// detail
// These constructors can only be called from SField.cpp // These constructors can only be called from SField.cpp
SField( SField(
@@ -306,83 +310,100 @@ public:
return knownCodeToField; return knownCodeToField;
} }
private: private:
static int num; static int num;
static std::map<int, SField const*> knownCodeToField; static std::map<int, SField const*> knownCodeToField;
static std::map<std::string, SField const*> knownNameToField; static std::map<std::string, SField const*> knownNameToField;
}; };
/** A field with a type known at compile time. */ /** A field with a type known at compile time. */
template <class T> template <class T>
struct TypedField : SField struct TypedField : SField
{ {
using type = T; using type = T;
template <class... Args> template <class... Args>
explicit TypedField(private_access_tag_t pat, Args&&... args); explicit TypedField(private_access_tag_t pat, Args&&... args);
}; };
/** Indicate std::optional field semantics. */ /** Indicate std::optional field semantics. */
template <class T> template <class T>
struct OptionaledField struct OptionaledField
{ {
TypedField<T> const* f; TypedField<T> const* f;
explicit OptionaledField(TypedField<T> const& f_) : f(&f_) explicit OptionaledField(TypedField<T> const& f_) : f(&f_)
{ {
} }
}; };
template <class T> template <class T>
inline OptionaledField<T> inline OptionaledField<T>
operator~(TypedField<T> const& f) operator~(TypedField<T> const& f)
{ {
return OptionaledField<T>(f); return OptionaledField<T>(f);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using SF_UINT8 = TypedField<STInteger<std::uint8_t>>; using SF_UINT8 = TypedField<STInteger<std::uint8_t>>;
using SF_UINT16 = TypedField<STInteger<std::uint16_t>>; using SF_UINT16 = TypedField<STInteger<std::uint16_t>>;
using SF_UINT32 = TypedField<STInteger<std::uint32_t>>; using SF_UINT32 = TypedField<STInteger<std::uint32_t>>;
using SF_UINT64 = TypedField<STInteger<std::uint64_t>>; using SF_UINT64 = TypedField<STInteger<std::uint64_t>>;
using SF_UINT96 = TypedField<STBitString<96>>; using SF_UINT96 = TypedField<STBitString<96>>;
using SF_UINT128 = TypedField<STBitString<128>>; using SF_UINT128 = TypedField<STBitString<128>>;
using SF_UINT160 = TypedField<STBitString<160>>; using SF_UINT160 = TypedField<STBitString<160>>;
using SF_UINT192 = TypedField<STBitString<192>>; using SF_UINT192 = TypedField<STBitString<192>>;
using SF_UINT256 = TypedField<STBitString<256>>; using SF_UINT256 = TypedField<STBitString<256>>;
using SF_UINT384 = TypedField<STBitString<384>>; using SF_UINT384 = TypedField<STBitString<384>>;
using SF_UINT512 = TypedField<STBitString<512>>; using SF_UINT512 = TypedField<STBitString<512>>;
using SF_ACCOUNT = TypedField<STAccount>; // These BIPS and TENTHBIPS values are serialized as the underlying type.
using SF_AMOUNT = TypedField<STAmount>; // The tag is only applied when deserialized.
using SF_ISSUE = TypedField<STIssue>; //
using SF_CURRENCY = TypedField<STCurrency>; // Basis points (bips) values:
using SF_NUMBER = TypedField<STNumber>; using SF_BIPS16 = TypedField<STInteger<Bips16>>;
using SF_VL = TypedField<STBlob>; using SF_BIPS32 = TypedField<STInteger<Bips32>>;
using SF_VECTOR256 = TypedField<STVector256>; // Tenth of a basis point values:
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>; using SF_TENTHBIPS16 = TypedField<STInteger<TenthBips16>>;
using SF_TENTHBIPS32 = TypedField<STInteger<TenthBips32>>;
//------------------------------------------------------------------------------ using SF_ACCOUNT = TypedField<STAccount>;
using SF_AMOUNT = TypedField<STAmount>;
using SF_ISSUE = TypedField<STIssue>;
using SF_CURRENCY = TypedField<STCurrency>;
using SF_NUMBER = TypedField<STNumber>;
using SF_VL = TypedField<STBlob>;
using SF_VECTOR256 = TypedField<STVector256>;
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
// 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") #pragma push_macro("UNTYPED_SFIELD")
#undef UNTYPED_SFIELD #undef UNTYPED_SFIELD
#pragma push_macro("TYPED_SFIELD") #pragma push_macro("TYPED_SFIELD")
#undef TYPED_SFIELD #undef TYPED_SFIELD
#pragma push_macro("INTERPRETED_SFIELD")
#undef INTERPRETED_SFIELD
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ #define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
extern SField const sfName; extern SField const sfName;
#define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ #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 sfInvalid;
extern SField const sfGeneric; extern SField const sfGeneric;
#include <xrpl/protocol/detail/sfields.macro> #include <xrpl/protocol/detail/sfields.macro>
#undef INTERPRETED_SFIELD
#pragma pop_macro("INTERPRETED_SFIELD")
#undef TYPED_SFIELD #undef TYPED_SFIELD
#pragma pop_macro("TYPED_SFIELD") #pragma pop_macro("TYPED_SFIELD")
#undef UNTYPED_SFIELD #undef UNTYPED_SFIELD

View File

@@ -21,6 +21,7 @@
#define RIPPLE_PROTOCOL_STINTEGER_H_INCLUDED #define RIPPLE_PROTOCOL_STINTEGER_H_INCLUDED
#include <xrpl/basics/CountedObject.h> #include <xrpl/basics/CountedObject.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STBase.h> #include <xrpl/protocol/STBase.h>
namespace ripple { namespace ripple {
@@ -81,6 +82,13 @@ using STUInt16 = STInteger<std::uint16_t>;
using STUInt32 = STInteger<std::uint32_t>; using STUInt32 = STInteger<std::uint32_t>;
using STUInt64 = STInteger<std::uint64_t>; using STUInt64 = STInteger<std::uint64_t>;
// Basis points (bips) values:
using SFBips16 = STInteger<Bips16>;
using SFBips32 = STInteger<Bips32>;
// Tenth of a basis point values:
using SFTenthBips16 = STInteger<TenthBips16>;
using SFTenthBips32 = STInteger<TenthBips32>;
template <typename Integer> template <typename Integer>
inline STInteger<Integer>::STInteger(Integer v) : value_(v) inline STInteger<Integer>::STInteger(Integer v) : value_(v)
{ {
@@ -122,7 +130,7 @@ template <typename Integer>
inline bool inline bool
STInteger<Integer>::isDefault() const STInteger<Integer>::isDefault() const
{ {
return value_ == 0; return value_ == Integer{};
} }
template <typename Integer> template <typename Integer>

View File

@@ -25,7 +25,6 @@
#include <xrpl/basics/chrono.h> #include <xrpl/basics/chrono.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/HashPrefix.h> #include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/SOTemplate.h> #include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
@@ -34,6 +33,7 @@
#include <xrpl/protocol/STIssue.h> #include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STPathSet.h> #include <xrpl/protocol/STPathSet.h>
#include <xrpl/protocol/STVector256.h> #include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/detail/STVar.h> #include <xrpl/protocol/detail/STVar.h>
#include <boost/iterator/transform_iterator.hpp> #include <boost/iterator/transform_iterator.hpp>
@@ -518,7 +518,8 @@ protected:
// Constraint += and -= ValueProxy operators // Constraint += and -= ValueProxy operators
// to value types that support arithmetic operations // to value types that support arithmetic operations
template <typename U> template <typename U>
concept IsArithmetic = std::is_arithmetic_v<U> || std::is_same_v<U, STAmount>; concept IsArithmetic = std::is_arithmetic_v<U> || std::is_same_v<U, STAmount> ||
std::is_same_v<U, STNumber> || std::is_same_v<U, Number>;
template <class T> template <class T>
class STObject::ValueProxy : public Proxy<T> class STObject::ValueProxy : public Proxy<T>

View File

@@ -22,10 +22,10 @@
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/PublicKey.h> #include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/STObject.h> #include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/SecretKey.h> #include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Units.h>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>

View File

@@ -16,8 +16,8 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
#ifndef BASICS_FEES_H_INCLUDED #ifndef PROTOCOL_UNITS_H_INCLUDED
#define BASICS_FEES_H_INCLUDED #define PROTOCOL_UNITS_H_INCLUDED
#include <xrpl/basics/safe_cast.h> #include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/Zero.h> #include <xrpl/beast/utility/Zero.h>
@@ -38,23 +38,23 @@
namespace ripple { namespace ripple {
namespace feeunit { namespace unit {
/** "drops" are the smallest divisible amount of XRP. This is what most /** "drops" are the smallest divisible amount of XRP. This is what most
of the code uses. */ of the code uses. */
struct dropTag; 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 /** "fee levels" are used by the transaction queue to compare the relative
cost of transactions that require different levels of effort to process. cost of transactions that require different levels of effort to process.
See also: src/ripple/app/misc/FeeEscalation.md#fee-level */ See also: src/ripple/app/misc/FeeEscalation.md#fee-level */
struct feelevelTag; 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. */ used for calculations in this header. */
struct unitlessTag; struct unitlessTag;
/** Units to represent basis points (bips) and 1/10 basis points */
class BipsTag;
class TenthBipsTag;
template <class T> template <class T>
using enable_if_unit_t = typename std::enable_if_t< using enable_if_unit_t = typename std::enable_if_t<
std::is_class_v<T> && std::is_object_v<typename T::unit_type> && std::is_class_v<T> && std::is_object_v<typename T::unit_type> &&
@@ -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 /** `is_usable_unit_v` is checked to ensure that only values with
known valid type tags can be used (sometimes transparently) in 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 all known tags, but more may be added in the future, and they
should not be added automatically unless determined to be should not be added automatically unless determined to be
appropriate. appropriate.
*/ */
template <class T, class = enable_if_unit_t<T>> template <class T, class = enable_if_unit_t<T>>
constexpr bool is_usable_unit_v = constexpr bool is_usable_unit_v =
std::is_same_v<typename T::unit_type, feeunitTag> ||
std::is_same_v<typename T::unit_type, feelevelTag> || std::is_same_v<typename T::unit_type, feelevelTag> ||
std::is_same_v<typename T::unit_type, unitlessTag> || std::is_same_v<typename T::unit_type, unitlessTag> ||
std::is_same_v<typename T::unit_type, dropTag>; std::is_same_v<typename T::unit_type, dropTag> ||
std::is_same_v<typename T::unit_type, BipsTag> ||
std::is_same_v<typename T::unit_type, TenthBipsTag>;
template <class UnitTag, class T> template <class UnitTag, class T>
class TaggedFee : private boost::totally_ordered<TaggedFee<UnitTag, T>>, class ValueUnit : private boost::totally_ordered<ValueUnit<UnitTag, T>>,
private boost::additive<TaggedFee<UnitTag, T>>, private boost::additive<ValueUnit<UnitTag, T>>,
private boost::equality_comparable<TaggedFee<UnitTag, T>, T>, private boost::equality_comparable<ValueUnit<UnitTag, T>, T>,
private boost::dividable<TaggedFee<UnitTag, T>, T>, private boost::dividable<ValueUnit<UnitTag, T>, T>,
private boost::modable<TaggedFee<UnitTag, T>, T>, private boost::modable<ValueUnit<UnitTag, T>, T>,
private boost::unit_steppable<TaggedFee<UnitTag, T>> private boost::unit_steppable<ValueUnit<UnitTag, T>>
{ {
public: public:
using unit_type = UnitTag; using unit_type = UnitTag;
using value_type = T; using value_type = T;
private: private:
value_type fee_; value_type value_;
protected: protected:
template <class Other> template <class Other>
@@ -95,44 +96,44 @@ protected:
std::is_arithmetic_v<Other> && std::is_arithmetic_v<value_type> && std::is_arithmetic_v<Other> && std::is_arithmetic_v<value_type> &&
std::is_convertible_v<Other, value_type>; std::is_convertible_v<Other, value_type>;
template <class OtherFee, class = enable_if_unit_t<OtherFee>> template <class OtherValue, class = enable_if_unit_t<OtherValue>>
static constexpr bool is_compatiblefee_v = static constexpr bool is_compatiblevalue_v =
is_compatible_v<typename OtherFee::value_type> && is_compatible_v<typename OtherValue::value_type> &&
std::is_same_v<UnitTag, typename OtherFee::unit_type>; std::is_same_v<UnitTag, typename OtherValue::unit_type>;
template <class Other> template <class Other>
using enable_if_compatible_t = using enable_if_compatible_t =
typename std::enable_if_t<is_compatible_v<Other>>; typename std::enable_if_t<is_compatible_v<Other>>;
template <class OtherFee> template <class OtherValue>
using enable_if_compatiblefee_t = using enable_if_compatiblevalue_t =
typename std::enable_if_t<is_compatiblefee_v<OtherFee>>; typename std::enable_if_t<is_compatiblevalue_v<OtherValue>>;
public: public:
TaggedFee() = default; ValueUnit() = default;
constexpr TaggedFee(TaggedFee const& other) = default; constexpr ValueUnit(ValueUnit const& other) = default;
constexpr TaggedFee& constexpr ValueUnit&
operator=(TaggedFee const& other) = default; 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) operator=(beast::Zero)
{ {
fee_ = 0; value_ = 0;
return *this; return *this;
} }
constexpr explicit TaggedFee(value_type fee) : fee_(fee) constexpr explicit ValueUnit(value_type value) : value_(value)
{ {
} }
TaggedFee& constexpr ValueUnit&
operator=(value_type fee) operator=(value_type value)
{ {
fee_ = fee; value_ = value;
return *this; return *this;
} }
@@ -144,153 +145,180 @@ public:
class = std::enable_if_t< class = std::enable_if_t<
is_compatible_v<Other> && is_compatible_v<Other> &&
is_safetocasttovalue_v<value_type, Other>>> is_safetocasttovalue_v<value_type, Other>>>
constexpr TaggedFee(TaggedFee<unit_type, Other> const& fee) constexpr ValueUnit(ValueUnit<unit_type, Other> const& value)
: TaggedFee(safe_cast<value_type>(fee.fee())) : ValueUnit(safe_cast<value_type>(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 operator*(value_type const& rhs) const
{ {
return TaggedFee{fee_ * rhs}; return ValueUnit{value_ * rhs};
} }
friend constexpr TaggedFee friend constexpr ValueUnit
operator*(value_type lhs, TaggedFee const& rhs) operator*(value_type lhs, ValueUnit const& rhs)
{ {
// multiplication is commutative // multiplication is commutative
return rhs * lhs; return rhs * lhs;
} }
constexpr value_type constexpr value_type
operator/(TaggedFee const& rhs) const operator/(ValueUnit const& rhs) const
{ {
return fee_ / rhs.fee_; return value_ / rhs.value_;
} }
TaggedFee& ValueUnit&
operator+=(TaggedFee const& other) operator+=(ValueUnit const& other)
{ {
fee_ += other.fee(); value_ += other.value();
return *this; return *this;
} }
TaggedFee& ValueUnit&
operator-=(TaggedFee const& other) operator-=(ValueUnit const& other)
{ {
fee_ -= other.fee(); value_ -= other.value();
return *this; return *this;
} }
TaggedFee& ValueUnit&
operator++() operator++()
{ {
++fee_; ++value_;
return *this; return *this;
} }
TaggedFee& ValueUnit&
operator--() operator--()
{ {
--fee_; --value_;
return *this; return *this;
} }
TaggedFee& ValueUnit&
operator*=(value_type const& rhs) operator*=(value_type const& rhs)
{ {
fee_ *= rhs; value_ *= rhs;
return *this; return *this;
} }
TaggedFee& ValueUnit&
operator/=(value_type const& rhs) operator/=(value_type const& rhs)
{ {
fee_ /= rhs; value_ /= rhs;
return *this; return *this;
} }
template <class transparent = value_type> template <class transparent = value_type>
std::enable_if_t<std::is_integral_v<transparent>, TaggedFee&> std::enable_if_t<std::is_integral_v<transparent>, ValueUnit&>
operator%=(value_type const& rhs) operator%=(value_type const& rhs)
{ {
fee_ %= rhs; value_ %= rhs;
return *this; return *this;
} }
TaggedFee ValueUnit
operator-() const operator-() const
{ {
static_assert( static_assert(
std::is_signed_v<T>, "- operator illegal on unsigned fee types"); std::is_signed_v<T>, "- operator illegal on unsigned value types");
return TaggedFee{-fee_}; return ValueUnit{-value_};
} }
bool constexpr bool
operator==(TaggedFee const& other) const operator==(ValueUnit const& other) const
{ {
return fee_ == other.fee_; return value_ == other.value_;
} }
template <class Other, class = enable_if_compatible_t<Other>> template <class Other, class = enable_if_compatible_t<Other>>
bool constexpr bool
operator==(TaggedFee<unit_type, Other> const& other) const operator==(ValueUnit<unit_type, Other> const& other) const
{ {
return fee_ == other.fee(); return value_ == other.value();
} }
bool constexpr bool
operator==(value_type other) const operator==(value_type other) const
{ {
return fee_ == other; return value_ == other;
} }
template <class Other, class = enable_if_compatible_t<Other>> template <class Other, class = enable_if_compatible_t<Other>>
bool constexpr bool
operator!=(TaggedFee<unit_type, Other> const& other) const operator!=(ValueUnit<unit_type, Other> const& other) const
{ {
return !operator==(other); return !operator==(other);
} }
bool constexpr bool
operator<(TaggedFee const& other) const operator<(ValueUnit const& other) const
{ {
return fee_ < other.fee_; return value_ < other.value_;
} }
/** Returns true if the amount is not zero */ /** Returns true if the amount is not zero */
explicit constexpr explicit constexpr
operator bool() const noexcept operator bool() const noexcept
{ {
return fee_ != 0; return value_ != 0;
} }
/** Return the sign of the amount */ /** Return the sign of the amount */
constexpr int constexpr int
signum() const noexcept signum() const noexcept
{ {
return (fee_ < 0) ? -1 : (fee_ ? 1 : 0); return (value_ < 0) ? -1 : (value_ ? 1 : 0);
} }
/** Returns the number of drops */ /** Returns the number of drops */
constexpr value_type constexpr value_type
fee() const fee() const
{ {
return fee_; return value_;
} }
template <class Other> template <class Other>
constexpr double constexpr double
decimalFromReference(TaggedFee<unit_type, Other> reference) const decimalFromReference(ValueUnit<unit_type, Other> reference) const
{ {
return static_cast<double>(fee_) / reference.fee(); return static_cast<double>(value_) / reference.value();
} }
// `is_usable_unit_v` is checked to ensure that only values with // `is_usable_unit_v` is checked to ensure that only values with
// known valid type tags can be converted to JSON. At the time // known valid type tags can be converted to JSON. At the time
// of implementation, that includes all known tags, but more may // of implementation, that includes all known tags, but more may
// be added in the future. // be added in the future.
std::enable_if_t<is_usable_unit_v<TaggedFee>, Json::Value> std::enable_if_t<is_usable_unit_v<ValueUnit>, Json::Value>
jsonClipped() const jsonClipped() const
{ {
if constexpr (std::is_integral_v<value_type>) if constexpr (std::is_integral_v<value_type>)
@@ -303,15 +331,15 @@ public:
constexpr auto min = std::numeric_limits<jsontype>::min(); constexpr auto min = std::numeric_limits<jsontype>::min();
constexpr auto max = std::numeric_limits<jsontype>::max(); constexpr auto max = std::numeric_limits<jsontype>::max();
if (fee_ < min) if (value_ < min)
return min; return min;
if (fee_ > max) if (value_ > max)
return max; return max;
return static_cast<jsontype>(fee_); return static_cast<jsontype>(value_);
} }
else else
{ {
return fee_; return value_;
} }
} }
@@ -322,30 +350,30 @@ public:
constexpr value_type constexpr value_type
value() const value() const
{ {
return fee_; return value_;
} }
friend std::istream& friend std::istream&
operator>>(std::istream& s, TaggedFee& val) operator>>(std::istream& s, ValueUnit& val)
{ {
s >> val.fee_; s >> val.value_;
return s; return s;
} }
}; };
// Output Fees as just their numeric value. // Output Values as just their numeric value.
template <class Char, class Traits, class UnitTag, class T> template <class Char, class Traits, class UnitTag, class T>
std::basic_ostream<Char, Traits>& std::basic_ostream<Char, Traits>&
operator<<(std::basic_ostream<Char, Traits>& os, const TaggedFee<UnitTag, T>& q) operator<<(std::basic_ostream<Char, Traits>& os, const ValueUnit<UnitTag, T>& q)
{ {
return os << q.value(); return os << q.value();
} }
template <class UnitTag, class T> template <class UnitTag, class T>
std::string std::string
to_string(TaggedFee<UnitTag, T> const& amount) to_string(ValueUnit<UnitTag, T> const& amount)
{ {
return std::to_string(amount.fee()); return std::to_string(amount.value());
} }
template <class Source, class = enable_if_unit_t<Source>> template <class Source, class = enable_if_unit_t<Source>>
@@ -408,10 +436,10 @@ using enable_muldiv_commute_t =
typename std::enable_if_t<can_muldiv_commute_v<Source1, Source2, Dest>>; typename std::enable_if_t<can_muldiv_commute_v<Source1, Source2, Dest>>;
template <class T> template <class T>
TaggedFee<unitlessTag, T> ValueUnit<unitlessTag, T>
scalar(T value) scalar(T value)
{ {
return TaggedFee<unitlessTag, T>{value}; return ValueUnit<unitlessTag, T>{value};
} }
template < template <
@@ -422,18 +450,17 @@ template <
std::optional<Dest> std::optional<Dest>
mulDivU(Source1 value, Dest mul, Source2 div) 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) if (value.value() < 0 || mul.value() < 0 || div.value() < 0)
{ {
// split the asserts so if one hits, the user can tell which // split the asserts so if one hits, the user can tell which
// without a debugger. // without a debugger.
XRPL_ASSERT( XRPL_ASSERT(
value.value() >= 0, value.value() >= 0, "ripple::unit::mulDivU : minimum value input");
"ripple::feeunit::mulDivU : minimum value input");
XRPL_ASSERT( XRPL_ASSERT(
mul.value() >= 0, "ripple::feeunit::mulDivU : minimum mul input"); mul.value() >= 0, "ripple::unit::mulDivU : minimum mul input");
XRPL_ASSERT( XRPL_ASSERT(
div.value() >= 0, "ripple::feeunit::mulDivU : minimum div input"); div.value() >= 0, "ripple::unit::mulDivU : minimum div input");
return std::nullopt; return std::nullopt;
} }
@@ -466,46 +493,57 @@ mulDivU(Source1 value, Dest mul, Source2 div)
return Dest{static_cast<desttype>(quotient)}; return Dest{static_cast<desttype>(quotient)};
} }
} // namespace feeunit } // namespace unit
// Fee Levels
template <class T> template <class T>
using FeeLevel = feeunit::TaggedFee<feeunit::feelevelTag, T>; using FeeLevel = unit::ValueUnit<unit::feelevelTag, T>;
using FeeLevel64 = FeeLevel<std::uint64_t>; using FeeLevel64 = FeeLevel<std::uint64_t>;
using FeeLevelDouble = FeeLevel<double>; using FeeLevelDouble = FeeLevel<double>;
// Basis points (Bips)
template <class T>
using Bips = unit::ValueUnit<unit::BipsTag, T>;
using Bips16 = Bips<std::uint16_t>;
using Bips32 = Bips<std::uint32_t>;
template <class T>
using TenthBips = unit::ValueUnit<unit::TenthBipsTag, T>;
using TenthBips16 = TenthBips<std::uint16_t>;
using TenthBips32 = TenthBips<std::uint32_t>;
template < template <
class Source1, class Source1,
class Source2, class Source2,
class Dest, class Dest,
class = feeunit::enable_muldiv_t<Source1, Source2, Dest>> class = unit::enable_muldiv_t<Source1, Source2, Dest>>
std::optional<Dest> std::optional<Dest>
mulDiv(Source1 value, Dest mul, Source2 div) mulDiv(Source1 value, Dest mul, Source2 div)
{ {
return feeunit::mulDivU(value, mul, div); return unit::mulDivU(value, mul, div);
} }
template < template <
class Source1, class Source1,
class Source2, class Source2,
class Dest, class Dest,
class = feeunit::enable_muldiv_commute_t<Source1, Source2, Dest>> class = unit::enable_muldiv_commute_t<Source1, Source2, Dest>>
std::optional<Dest> std::optional<Dest>
mulDiv(Dest value, Source1 mul, Source2 div) mulDiv(Dest value, Source1 mul, Source2 div)
{ {
// Multiplication is commutative // Multiplication is commutative
return feeunit::mulDivU(mul, value, div); return unit::mulDivU(mul, value, div);
} }
template <class Dest, class = feeunit::enable_muldiv_dest_t<Dest>> template <class Dest, class = unit::enable_muldiv_dest_t<Dest>>
std::optional<Dest> std::optional<Dest>
mulDiv(std::uint64_t value, Dest mul, std::uint64_t div) mulDiv(std::uint64_t value, Dest mul, std::uint64_t div)
{ {
// Give the scalars a non-tag so the // Give the scalars a non-tag so the
// unit-handling version gets called. // 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 <class Dest, class = feeunit::enable_muldiv_dest_t<Dest>> template <class Dest, class = unit::enable_muldiv_dest_t<Dest>>
std::optional<Dest> std::optional<Dest>
mulDiv(Dest value, std::uint64_t mul, std::uint64_t div) 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 < template <
class Source1, class Source1,
class Source2, class Source2,
class = feeunit::enable_muldiv_sources_t<Source1, Source2>> class = unit::enable_muldiv_sources_t<Source1, Source2>>
std::optional<std::uint64_t> std::optional<std::uint64_t>
mulDiv(Source1 value, std::uint64_t mul, Source2 div) mulDiv(Source1 value, std::uint64_t mul, Source2 div)
{ {
// Give the scalars a dimensionless unit so the // Give the scalars a dimensionless unit so the
// unit-handling version gets called. // 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) if (!unitresult)
return std::nullopt; return std::nullopt;
@@ -533,7 +571,7 @@ mulDiv(Source1 value, std::uint64_t mul, Source2 div)
template < template <
class Source1, class Source1,
class Source2, class Source2,
class = feeunit::enable_muldiv_sources_t<Source1, Source2>> class = unit::enable_muldiv_sources_t<Source1, Source2>>
std::optional<std::uint64_t> std::optional<std::uint64_t>
mulDiv(std::uint64_t value, Source1 mul, Source2 div) mulDiv(std::uint64_t value, Source1 mul, Source2 div)
{ {
@@ -567,4 +605,4 @@ unsafe_cast(Src s) noexcept
} // namespace ripple } // namespace ripple
#endif // BASICS_FEES_H_INCLUDED #endif // PROTOCOL_UNITS_H_INCLUDED

View File

@@ -24,7 +24,7 @@
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Zero.h> #include <xrpl/beast/utility/Zero.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
#include <xrpl/protocol/FeeUnits.h> #include <xrpl/protocol/Units.h>
#include <boost/multiprecision/cpp_int.hpp> #include <boost/multiprecision/cpp_int.hpp>
#include <boost/operators.hpp> #include <boost/operators.hpp>
@@ -42,7 +42,7 @@ class XRPAmount : private boost::totally_ordered<XRPAmount>,
private boost::additive<XRPAmount, std::int64_t> private boost::additive<XRPAmount, std::int64_t>
{ {
public: public:
using unit_type = feeunit::dropTag; using unit_type = unit::dropTag;
using value_type = std::int64_t; using value_type = std::int64_t;
private: private:

View File

@@ -61,7 +61,7 @@ TYPED_SFIELD(sfHookEmitCount, UINT16, 18)
TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19) TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
TYPED_SFIELD(sfHookApiVersion, UINT16, 20) TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
TYPED_SFIELD(sfLedgerFixType, UINT16, 21) TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) INTERPRETED_SFIELD(sfManagementFeeRate, UINT16, 22, TENTHBIPS16)
// 32-bit integers (common) // 32-bit integers (common)
TYPED_SFIELD(sfNetworkID, UINT32, 1) TYPED_SFIELD(sfNetworkID, UINT32, 1)
@@ -123,12 +123,12 @@ TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 55)
TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 56) TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 56)
TYPED_SFIELD(sfPaymentRemaining, UINT32, 57) TYPED_SFIELD(sfPaymentRemaining, UINT32, 57)
TYPED_SFIELD(sfPaymentTotal, UINT32, 58) TYPED_SFIELD(sfPaymentTotal, UINT32, 58)
TYPED_SFIELD(sfCoverRateMinimum, UINT32, 59) INTERPRETED_SFIELD(sfCoverRateMinimum, UINT32, 59, TENTHBIPS32)
TYPED_SFIELD(sfCoverRateLiquidation, UINT32, 60) INTERPRETED_SFIELD(sfCoverRateLiquidation, UINT32, 60, TENTHBIPS32)
TYPED_SFIELD(sfInterestRate, UINT32, 61) INTERPRETED_SFIELD(sfInterestRate, UINT32, 61, TENTHBIPS32)
TYPED_SFIELD(sfLateInterestRate, UINT32, 62) INTERPRETED_SFIELD(sfLateInterestRate, UINT32, 62, TENTHBIPS32)
TYPED_SFIELD(sfCloseInterestRate, UINT32, 63) INTERPRETED_SFIELD(sfCloseInterestRate, UINT32, 63, TENTHBIPS32)
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 64) INTERPRETED_SFIELD(sfOverpaymentInterestRate, UINT32, 64, TENTHBIPS32)
// 64-bit integers (common) // 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1) TYPED_SFIELD(sfIndexNext, UINT64, 1)

View File

@@ -739,9 +739,11 @@ TRANSACTION(ttLOAN_SET, 78, LoanSet, noPriv, ({
{sfLoanServiceFee, soeOPTIONAL}, {sfLoanServiceFee, soeOPTIONAL},
{sfLatePaymentFee, soeOPTIONAL}, {sfLatePaymentFee, soeOPTIONAL},
{sfClosePaymentFee, soeOPTIONAL}, {sfClosePaymentFee, soeOPTIONAL},
{sfOverpaymentFee, soeOPTIONAL},
{sfInterestRate, soeOPTIONAL}, {sfInterestRate, soeOPTIONAL},
{sfLateInterestRate, soeOPTIONAL}, {sfLateInterestRate, soeOPTIONAL},
{sfCloseInterestRate, soeOPTIONAL}, {sfCloseInterestRate, soeOPTIONAL},
{sfOverpaymentInterestRate, soeOPTIONAL},
{sfPrincipalRequested, soeREQUIRED}, {sfPrincipalRequested, soeREQUIRED},
{sfStartDate, soeREQUIRED}, {sfStartDate, soeREQUIRED},
{sfPaymentTotal, soeOPTIONAL}, {sfPaymentTotal, soeOPTIONAL},

View File

@@ -559,9 +559,12 @@ loanbroker(AccountID const& owner, std::uint32_t seq) noexcept
} }
Keylet 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 Keylet

View File

@@ -54,6 +54,8 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
#undef UNTYPED_SFIELD #undef UNTYPED_SFIELD
#pragma push_macro("TYPED_SFIELD") #pragma push_macro("TYPED_SFIELD")
#undef TYPED_SFIELD #undef TYPED_SFIELD
#pragma push_macro("INTERPRETED_SFIELD")
#undef INTERPRETED_SFIELD
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \ #define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
SField const sfName( \ SField const sfName( \
@@ -69,6 +71,13 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
fieldValue, \ fieldValue, \
std::string_view(#sfName).substr(2).data(), \ std::string_view(#sfName).substr(2).data(), \
##__VA_ARGS__); ##__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. // SFields which, for historical reasons, do not follow naming conventions.
SField const sfInvalid(access, -1); SField const sfInvalid(access, -1);
@@ -80,6 +89,8 @@ SField const sfIndex(access, STI_UINT256, 258, "index");
#include <xrpl/protocol/detail/sfields.macro> #include <xrpl/protocol/detail/sfields.macro>
#undef INTERPRETED_SFIELD
#pragma pop_macro("INTERPRETED_SFIELD")
#undef TYPED_SFIELD #undef TYPED_SFIELD
#pragma pop_macro("TYPED_SFIELD") #pragma pop_macro("TYPED_SFIELD")
#undef UNTYPED_SFIELD #undef UNTYPED_SFIELD

View File

@@ -686,7 +686,10 @@ STObject
STObject::getFieldObject(SField const& field) const STObject::getFieldObject(SField const& field) const
{ {
STObject const empty{field}; STObject const empty{field};
return getFieldByConstRef<STObject>(field, empty); auto ret = getFieldByConstRef<STObject>(field, empty);
if (ret != empty)
ret.applyTemplateFromSField(field);
return ret;
} }
const STArray& const STArray&

View File

@@ -75,7 +75,14 @@ class LoanBroker_test : public beast::unit_test::suite
env(set(alice, keylet.key), fee(increment), ter(temDISABLED)); env(set(alice, keylet.key), fee(increment), ter(temDISABLED));
auto const brokerKeylet = auto const brokerKeylet =
keylet::loanbroker(alice.id(), env.seq(alice)); 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)); env(del(alice, brokerKeylet.key), ter(temDISABLED));
}; };
failAll(all - featureMPTokensV1); failAll(all - featureMPTokensV1);
@@ -421,7 +428,7 @@ class LoanBroker_test : public beast::unit_test::suite
ter(tecNO_PERMISSION)); ter(tecNO_PERMISSION));
// sfManagementFeeRate: too big // sfManagementFeeRate: too big
env(set(evan, vault.vaultID), env(set(evan, vault.vaultID),
managementFeeRate(maxManagementFeeRate + 1), managementFeeRate(maxManagementFeeRate + TenthBips16(10)),
fee(increment), fee(increment),
ter(temINVALID)); ter(temINVALID));
// sfCoverRateMinimum: good value, bad account // sfCoverRateMinimum: good value, bad account
@@ -555,10 +562,10 @@ class LoanBroker_test : public beast::unit_test::suite
return env.jt( return env.jt(
jv, jv,
data(testData), data(testData),
managementFeeRate(123), managementFeeRate(TenthBips16(123)),
debtMaximum(Number(9)), debtMaximum(Number(9)),
coverRateMinimum(100), coverRateMinimum(TenthBips32(100)),
coverRateLiquidation(200)); coverRateLiquidation(TenthBips32(200)));
}, },
[&](SLE::const_ref broker) { [&](SLE::const_ref broker) {
// Extra checks // Extra checks

View File

@@ -17,13 +17,13 @@
*/ */
#include <xrpl/beast/unit_test.h> #include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/SystemParameters.h> #include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/Units.h>
namespace ripple { namespace ripple {
namespace test { namespace test {
class feeunits_test : public beast::unit_test::suite class units_test : public beast::unit_test::suite
{ {
private: private:
void void
@@ -35,16 +35,16 @@ private:
XRPAmount x{100}; XRPAmount x{100};
BEAST_EXPECT(x.drops() == 100); BEAST_EXPECT(x.drops() == 100);
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(x)::unit_type, feeunit::dropTag>)); (std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
auto y = 4u * x; auto y = 4u * x;
BEAST_EXPECT(y.value() == 400); BEAST_EXPECT(y.value() == 400);
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>)); (std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
auto z = 4 * y; auto z = 4 * y;
BEAST_EXPECT(z.value() == 1600); BEAST_EXPECT(z.value() == 1600);
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(z)::unit_type, feeunit::dropTag>)); (std::is_same_v<decltype(z)::unit_type, unit::dropTag>));
FeeLevel32 f{10}; FeeLevel32 f{10};
FeeLevel32 baseFee{100}; FeeLevel32 baseFee{100};
@@ -55,7 +55,7 @@ private:
BEAST_EXPECT(drops.value() == 1000); BEAST_EXPECT(drops.value() == 1000);
BEAST_EXPECT((std::is_same_v< BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>::unit_type, std::remove_reference_t<decltype(*drops)>::unit_type,
feeunit::dropTag>)); unit::dropTag>));
BEAST_EXPECT((std::is_same_v< BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>, std::remove_reference_t<decltype(*drops)>,
@@ -65,11 +65,11 @@ private:
XRPAmount x{100}; XRPAmount x{100};
BEAST_EXPECT(x.value() == 100); BEAST_EXPECT(x.value() == 100);
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(x)::unit_type, feeunit::dropTag>)); (std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
auto y = 4u * x; auto y = 4u * x;
BEAST_EXPECT(y.value() == 400); BEAST_EXPECT(y.value() == 400);
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>)); (std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
FeeLevel64 f{10}; FeeLevel64 f{10};
FeeLevel64 baseFee{100}; FeeLevel64 baseFee{100};
@@ -80,7 +80,7 @@ private:
BEAST_EXPECT(drops.value() == 1000); BEAST_EXPECT(drops.value() == 1000);
BEAST_EXPECT((std::is_same_v< BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>::unit_type, std::remove_reference_t<decltype(*drops)>::unit_type,
feeunit::dropTag>)); unit::dropTag>));
BEAST_EXPECT((std::is_same_v< BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>, std::remove_reference_t<decltype(*drops)>,
XRPAmount>)); XRPAmount>));
@@ -89,12 +89,12 @@ private:
FeeLevel64 x{1024}; FeeLevel64 x{1024};
BEAST_EXPECT(x.value() == 1024); BEAST_EXPECT(x.value() == 1024);
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(x)::unit_type, feeunit::feelevelTag>)); (std::is_same_v<decltype(x)::unit_type, unit::feelevelTag>));
std::uint64_t m = 4; std::uint64_t m = 4;
auto y = m * x; auto y = m * x;
BEAST_EXPECT(y.value() == 4096); BEAST_EXPECT(y.value() == 4096);
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(y)::unit_type, feeunit::feelevelTag>)); (std::is_same_v<decltype(y)::unit_type, unit::feelevelTag>));
XRPAmount basefee{10}; XRPAmount basefee{10};
FeeLevel64 referencefee{256}; FeeLevel64 referencefee{256};
@@ -105,7 +105,7 @@ private:
BEAST_EXPECT(drops.value() == 40); BEAST_EXPECT(drops.value() == 40);
BEAST_EXPECT((std::is_same_v< BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>::unit_type, std::remove_reference_t<decltype(*drops)>::unit_type,
feeunit::dropTag>)); unit::dropTag>));
BEAST_EXPECT((std::is_same_v< BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>, std::remove_reference_t<decltype(*drops)>,
XRPAmount>)); XRPAmount>));
@@ -181,7 +181,7 @@ private:
void void
testFunctions() 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. // since some of them are templated, but not used anywhere else.
using FeeLevel32 = FeeLevel<std::uint32_t>; using FeeLevel32 = FeeLevel<std::uint32_t>;
@@ -191,8 +191,8 @@ private:
return FeeLevel64{x}; return FeeLevel64{x};
}; };
[[maybe_unused]]
FeeLevel64 defaulted; FeeLevel64 defaulted;
(void)defaulted;
FeeLevel64 test{0}; FeeLevel64 test{0};
BEAST_EXPECT(test.fee() == 0); BEAST_EXPECT(test.fee() == 0);
@@ -278,8 +278,8 @@ private:
return FeeLevelDouble{x}; return FeeLevelDouble{x};
}; };
[[maybe_unused]]
FeeLevelDouble defaulted; FeeLevelDouble defaulted;
(void)defaulted;
FeeLevelDouble test{0}; FeeLevelDouble test{0};
BEAST_EXPECT(test.fee() == 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 test
} // namespace ripple } // namespace ripple

View File

@@ -54,7 +54,9 @@ struct JTx
bool fill_sig = true; bool fill_sig = true;
bool fill_netid = true; bool fill_netid = true;
std::shared_ptr<STTx const> stx; std::shared_ptr<STTx const> stx;
// TODO: Remove
std::function<void(Env&, JTx&)> signer; std::function<void(Env&, JTx&)> signer;
std::vector<std::function<void(Env&, JTx&)>> signers;
JTx() = default; JTx() = default;
JTx(JTx const&) = default; JTx(JTx const&) = default;

View File

@@ -28,6 +28,7 @@
#include <xrpl/protocol/Quality.h> #include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/STNumber.h> #include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TxFlags.h> #include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
#include <vector> #include <vector>
@@ -164,6 +165,37 @@ struct blobField : public JTxField<SF_VL, std::string>
} }
}; };
template <class SField, class UnitTag, class ValueType>
struct valueUnitField
: public JTxField<SField, unit::ValueUnit<UnitTag, ValueType>, ValueType>
{
using SF = SField;
using SV = unit::ValueUnit<UnitTag, ValueType>;
using OV = ValueType;
using base = JTxField<SF, SV, OV>;
static_assert(
std::is_same_v<SV, typename SField::type::value_type::value_type>);
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 <class JTxField> template <class JTxField>
struct JTxFieldWrapper 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<valueUnitField<SField, UnitTag, ValueType>>;
template <class SField, class StoredValue = typename SField::type::value_type> template <class SField, class StoredValue = typename SField::type::value_type>
using simpleField = JTxFieldWrapper<JTxField<SField, StoredValue>>; using simpleField = JTxFieldWrapper<JTxField<SField, StoredValue>>;
@@ -627,17 +666,32 @@ auto const loanBrokerID = JTxFieldWrapper<uint256Field>(sfLoanBrokerID);
auto const data = JTxFieldWrapper<blobField>(sfData); auto const data = JTxFieldWrapper<blobField>(sfData);
auto const managementFeeRate = simpleField<SF_UINT16>(sfManagementFeeRate); auto const managementFeeRate =
valueUnitWrapper<SF_TENTHBIPS16>(sfManagementFeeRate);
auto const debtMaximum = simpleField<SF_NUMBER>(sfDebtMaximum); auto const debtMaximum = simpleField<SF_NUMBER>(sfDebtMaximum);
auto const coverRateMinimum = simpleField<SF_UINT32>(sfCoverRateMinimum); auto const coverRateMinimum =
valueUnitWrapper<SF_TENTHBIPS32>(sfCoverRateMinimum);
auto const coverRateLiquidation = auto const coverRateLiquidation =
simpleField<SF_UINT32>(sfCoverRateLiquidation); simpleField<SF_TENTHBIPS32>(sfCoverRateLiquidation);
} // namespace loanBroker } // 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 jtx
} // namespace test } // namespace test
} // namespace ripple } // namespace ripple

View File

@@ -24,9 +24,9 @@
#include <test/jtx/tags.h> #include <test/jtx/tags.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/Issue.h> #include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/Units.h>
#include <cstdint> #include <cstdint>
#include <ostream> #include <ostream>

View File

@@ -470,6 +470,28 @@ coverWithdraw(
} // namespace loanBroker } // 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 jtx
} // namespace test } // namespace test
} // namespace ripple } // namespace ripple

View File

@@ -1382,7 +1382,11 @@ class Invariants_test : public beast::unit_test::suite
sle->at(sfOwner) = sle->at(sfAccount); sle->at(sfOwner) = sle->at(sfAccount);
}, },
[](SLE::pointer& sle) { [](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) { sle->at(sfCoverRateMinimum) += 1; },
[](SLE::pointer& sle) { [](SLE::pointer& sle) {

View File

@@ -805,7 +805,7 @@ class Simulate_test : public beast::unit_test::suite
testTx(env, tx, testSimulation); testTx(env, tx, testSimulation);
tx[sfSigningPubKey] = ""; tx[sfSigningPubKey] = "";
tx[sfTxnSignature] = ""; tx[sfTxnSignature] = "x";
tx[sfSequence] = env.seq(env.master); tx[sfSequence] = env.seq(env.master);
tx[sfFee] = env.current()->fees().base.jsonClipped().asString(); tx[sfFee] = env.current()->fees().base.jsonClipped().asString();

View File

@@ -23,7 +23,7 @@
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h> #include <xrpl/basics/safe_cast.h>
#include <xrpl/protocol/FeeUnits.h> #include <xrpl/protocol/Units.h>
#include <cstdint> #include <cstdint>

View File

@@ -30,10 +30,10 @@
#include <xrpl/basics/mulDiv.h> #include <xrpl/basics/mulDiv.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h> #include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TxFlags.h> #include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/Units.h>
namespace ripple { namespace ripple {

View File

@@ -26,11 +26,11 @@
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/STArray.h> #include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STNumber.h> #include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/SystemParameters.h> #include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/TxFormats.h> #include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/nftPageMask.h> #include <xrpl/protocol/nftPageMask.h>
namespace ripple { namespace ripple {

View File

@@ -125,6 +125,8 @@ LoanBrokerCoverDeposit::doApply()
auto const amount = tx[sfAmount]; auto const amount = tx[sfAmount];
auto broker = view().peek(keylet::loanbroker(brokerID)); auto broker = view().peek(keylet::loanbroker(brokerID));
if (!broker)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const brokerPseudoID = broker->at(sfAccount); auto const brokerPseudoID = broker->at(sfAccount);

View File

@@ -138,6 +138,8 @@ LoanBrokerCoverWithdraw::doApply()
auto const amount = tx[sfAmount]; auto const amount = tx[sfAmount];
auto broker = view().peek(keylet::loanbroker(brokerID)); auto broker = view().peek(keylet::loanbroker(brokerID));
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerPseudoID = broker->at(sfAccount); auto const brokerPseudoID = broker->at(sfAccount);

View File

@@ -96,8 +96,12 @@ LoanBrokerDelete::doApply()
// Delete the loan broker // Delete the loan broker
auto broker = view().peek(keylet::loanbroker(brokerID)); auto broker = view().peek(keylet::loanbroker(brokerID));
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultID = broker->at(sfVaultID); auto const vaultID = broker->at(sfVaultID);
auto const sleVault = view().read(keylet::vault(vaultID)); auto const sleVault = view().read(keylet::vault(vaultID));
if (!sleVault)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudoID = sleVault->at(sfAccount); auto const vaultPseudoID = sleVault->at(sfAccount);
auto const vaultAsset = sleVault->at(sfAsset); auto const vaultAsset = sleVault->at(sfAsset);

View File

@@ -155,6 +155,8 @@ LoanBrokerSet::doApply()
{ {
// Modify an existing LoanBroker // Modify an existing LoanBroker
auto broker = view.peek(keylet::loanbroker(*brokerID)); auto broker = view.peek(keylet::loanbroker(*brokerID));
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
if (auto const data = tx[~sfData]) if (auto const data = tx[~sfData])
broker->at(sfData) = *data; broker->at(sfData) = *data;
@@ -172,6 +174,8 @@ LoanBrokerSet::doApply()
auto const sequence = tx.getSeqValue(); auto const sequence = tx.getSeqValue();
auto owner = view.peek(keylet::account(account_)); auto owner = view.peek(keylet::account(account_));
if (!owner)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto broker = auto broker =
std::make_shared<SLE>(keylet::loanbroker(account_, sequence)); std::make_shared<SLE>(keylet::loanbroker(account_, sequence));
@@ -180,10 +184,14 @@ LoanBrokerSet::doApply()
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode)) if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
return ter; 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_); adjustOwnerCount(view, owner, 1, j_);
auto ownerCount = owner->at(sfOwnerCount); auto ownerCount = owner->at(sfOwnerCount);
if (mPriorBalance < view.fees().accountReserve(ownerCount)) if (mPriorBalance < view.fees().accountReserve(ownerCount))
return tecINSUFFICIENT_RESERVE; return tecINSUFFICIENT_RESERVE;
*/
auto maybePseudo = createPseudoAccount( auto maybePseudo = createPseudoAccount(
view, broker->key(), PseudoAccountOwnerType::LoanBroker); view, broker->key(), PseudoAccountOwnerType::LoanBroker);

View File

@@ -33,6 +33,7 @@
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h> #include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SField.h> #include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
@@ -63,14 +64,9 @@ LoanSet::doPreflight(PreflightContext const& ctx)
auto const& tx = ctx.tx; auto const& tx = ctx.tx;
auto const counterPartySig = ctx.tx.getFieldObject(sfCounterpartySignature); auto const counterPartySig = ctx.tx.getFieldObject(sfCounterpartySignature);
// Copied from preflight1 if (auto const ret =
// TODO: Refactor into a helper function? ripple::detail::preflightCheckSigningKey(counterPartySig, ctx.j))
if (auto const spk = counterPartySig.getFieldVL(sfSigningPubKey); return ret;
!spk.empty() && !publicKeyType(makeSlice(spk)))
{
JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
return temBAD_SIGNATURE;
}
if (auto const data = tx[~sfData]; data && !data->empty() && if (auto const data = tx[~sfData]; data && !data->empty() &&
!validDataLength(tx[~sfData], maxDataPayloadLength)) !validDataLength(tx[~sfData], maxDataPayloadLength))
@@ -96,34 +92,9 @@ LoanSet::doPreflight(PreflightContext const& ctx)
return temINVALID; return temINVALID;
// Copied from preflight2 // Copied from preflight2
// TODO: Refactor into a helper function? if (auto const ret = ripple::detail::preflightCheckSimulateKeys(
if (ctx.flags & tapDRY_RUN) // simulation ctx.flags, counterPartySig, ctx.j))
{ return *ret;
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;
}
return tesSUCCESS; return tesSUCCESS;
} }
@@ -152,7 +123,6 @@ LoanSet::checkSign(PreclaimContext const& ctx)
auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature); auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature);
return Transactor::checkSign(ctx, *counterSigner, counterSig); return Transactor::checkSign(ctx, *counterSigner, counterSig);
} }
}
XRPAmount XRPAmount
LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx) LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx)
@@ -176,120 +146,247 @@ LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx)
TER TER
LoanSet::preclaim(PreclaimContext const& ctx) LoanSet::preclaim(PreclaimContext const& ctx)
{ {
return temDISABLED;
auto const& tx = ctx.tx; 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]; 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)); JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
if (!sle)
{
JLOG(ctx.j.warn()) << "Loan does not exist.";
return tecNO_ENTRY; return tecNO_ENTRY;
} }
if (tx[sfVaultID] != sle->at(sfVaultID)) auto const brokerOwner = brokerSle->at(sfOwner);
if (!tx.isFieldPresent(sfCounterparty) && account != brokerOwner)
{ {
JLOG(ctx.j.warn()) << "Can not change VaultID on an existing Loan."; JLOG(ctx.j.warn())
<< "Counterparty is not the owner of the LoanBroker.";
return tecNO_PERMISSION; return tecNO_PERMISSION;
} }
if (account != sle->at(sfOwner)) auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
auto const borrower = counterparty == brokerOwner ? account : counterparty;
if (account != brokerOwner && counterparty != brokerOwner)
{ {
JLOG(ctx.j.warn()) << "Account is not the owner of the Loan."; JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner "
"of the LoanBroker.";
return tecNO_PERMISSION; return tecNO_PERMISSION;
} }
}
else if (auto const borrowerSle = ctx.view.read(keylet::account(borrower));
!borrowerSle)
{ {
auto const vaultID = tx[sfVaultID]; JLOG(ctx.j.warn()) << "Borrower does not exist.";
auto const sleVault = ctx.view.read(keylet::vault(vaultID));
if (!sleVault)
{
JLOG(ctx.j.warn()) << "Vault does not exist.";
return tecNO_ENTRY; return tecNO_ENTRY;
} }
if (account != sleVault->at(sfOwner))
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()) << "Account is not the owner of the Vault."; JLOG(ctx.j.warn()) << "One of the affected accounts is frozen.";
return tecNO_PERMISSION; return asset.holds<Issue>() ? tecFROZEN : tecLOCKED;
} }
if (asset.holds<Issue>())
{
auto const issue = asset.get<Issue>();
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; return tesSUCCESS;
} }
TER TER
LoanSet::doApply() LoanSet::doApply()
{ {
return temDISABLED;
auto const& tx = ctx_.tx; auto const& tx = ctx_.tx;
auto& view = ctx_.view(); auto& view = ctx_.view();
#if 0 auto const brokerID = tx[sfLoanBrokerID];
if (auto const ID = tx[~sfLoanID])
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 return tefBAD_LEDGER; // LCOV_EXCL_LINE
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();
} }
else
{
// 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 const brokerPseudo = brokerSle->at(sfAccount);
auto loan = std::make_shared<SLE>(keylet::loan(account_, sequence)); 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{});
if (auto const ter = dirLink(view, account_, )) adjustOwnerCount(view, borrowerSle, 1, j_);
return ter; auto ownerCount = borrowerSle->at(sfOwnerCount);
if (auto const ter = dirLink(view, vaultPseudoID, , sfVaultNode))
return ter;
adjustOwnerCount(view, owner, 1, j_);
auto ownerCount = owner->at(sfOwnerCount);
if (mPriorBalance < view.fees().accountReserve(ownerCount)) if (mPriorBalance < view.fees().accountReserve(ownerCount))
return tecINSUFFICIENT_RESERVE; return tecINSUFFICIENT_RESERVE;
auto maybePseudo = // Create a holding for the borrower if one does not already exist.
createPseudoAccount(view, loan->key(), PseudoAccountOwnerType::Loan);
if (!maybePseudo)
return maybePseudo.error();
auto& pseudo = *maybePseudo;
auto pseudoId = pseudo->at(sfAccount);
if (auto ter = addEmptyHolding( // Account for the origination fee using two payments
view, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_)) //
// 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 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 = accountSend(
view,
vaultPseudo,
brokerOwner,
STAmount{vaultAsset, *originationFee},
j_,
WaiveTransferFee::Yes))
return ter;
}
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<SLE>(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; return ter;
// Initialize data fields: view.update(brokerSle);
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
return tesSUCCESS; return tesSUCCESS;
} }

View File

@@ -87,6 +87,22 @@ preflight0(PreflightContext const& ctx, std::uint32_t flagMask)
namespace detail { 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 */ /** Performs early sanity checks on the account and fee fields */
NotTEC NotTEC
preflight1(PreflightContext const& ctx, std::uint32_t flagMask) preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
@@ -118,13 +134,8 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
return temBAD_FEE; return temBAD_FEE;
} }
auto const spk = ctx.tx.getSigningPubKey(); if (auto const ret = preflightCheckSigningKey(ctx.tx, ctx.j))
return ret;
if (!spk.empty() && !publicKeyType(makeSlice(spk)))
{
JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
return temBAD_SIGNATURE;
}
// An AccountTxnID field constrains transaction ordering more than the // An AccountTxnID field constrains transaction ordering more than the
// Sequence field. Tickets, on the other hand, reduce ordering // Sequence field. Tickets, on the other hand, reduce ordering
@@ -139,26 +150,28 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
return tesSUCCESS; return tesSUCCESS;
} }
/** Checks whether the signature appears valid */ std::optional<NotTEC>
NotTEC preflightCheckSimulateKeys(
preflight2(PreflightContext const& ctx) 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 // NOTE: This code should never be hit because it's checked in the
// `simulate` RPC // `simulate` RPC
return temINVALID; // LCOV_EXCL_LINE return temINVALID; // LCOV_EXCL_LINE
} }
if (!ctx.tx.isFieldPresent(sfSigners)) if (!sigObject.isFieldPresent(sfSigners))
{ {
// no signers, no signature - a valid simulation // no signers, no signature - a valid simulation
return tesSUCCESS; return tesSUCCESS;
} }
for (auto const& signer : ctx.tx.getFieldArray(sfSigners)) for (auto const& signer : sigObject.getFieldArray(sfSigners))
{ {
if (signer.isFieldPresent(sfTxnSignature) && if (signer.isFieldPresent(sfTxnSignature) &&
!signer[sfTxnSignature].empty()) !signer[sfTxnSignature].empty())
@@ -170,6 +183,17 @@ preflight2(PreflightContext const& ctx)
} }
return tesSUCCESS; 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( auto const sigValid = checkValidity(
ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config()); ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config());

View File

@@ -262,6 +262,14 @@ NotTEC
preflight0(PreflightContext const& ctx, std::uint32_t flagMask); preflight0(PreflightContext const& ctx, std::uint32_t flagMask);
namespace detail { 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. /** Performs early sanity checks on the account and fee fields.
(And passes flagMask to preflight0) (And passes flagMask to preflight0)
@@ -269,6 +277,16 @@ namespace detail {
NotTEC NotTEC
preflight1(PreflightContext const& ctx, std::uint32_t flagMask); 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<NotTEC>
preflightCheckSimulateKeys(
ApplyFlags flags,
STObject const& sigObject,
beast::Journal j);
/** Checks whether the signature appears valid */ /** Checks whether the signature appears valid */
NotTEC NotTEC
preflight2(PreflightContext const& ctx); preflight2(PreflightContext const& ctx);